1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: GPL-2.0+
3#
4# Tests for U-Boot-specific checkpatch.pl features
5#
6# Copyright (c) 2011 The Chromium OS Authors.
7#
8
9import os
10import tempfile
11import unittest
12
13from patman import checkpatch
14from patman import gitutil
15from patman import patchstream
16from patman import series
17from patman import commit
18
19
20class Line:
21    """Single changed line in one file in a patch
22
23    Args:
24        fname (str): Filename containing the added line
25        text (str): Text of the added line
26    """
27    def __init__(self, fname, text):
28        self.fname = fname
29        self.text = text
30
31
32class PatchMaker:
33    """Makes a patch for checking with checkpatch.pl
34
35    The idea here is to create a patch which adds one line in one file,
36    intended to provoke a checkpatch error or warning. The base patch is empty
37    (i.e. invalid), so you should call add_line() to add at least one line.
38    """
39    def __init__(self):
40        """Set up the PatchMaker object
41
42        Properties:
43            lines (list of Line): List of lines to add to the patch. Note that
44                each line has both a file and some text associated with it,
45                since for simplicity we just add a single line for each file
46        """
47        self.lines = []
48
49    def add_line(self, fname, text):
50        """Add to the list of filename/line pairs"""
51        self.lines.append(Line(fname, text))
52
53    def get_patch_text(self):
54        """Build the patch text
55
56        Takes a base patch and adds a diffstat and patch for each filename/line
57        pair in the list.
58
59        Returns:
60            str: Patch text ready for submission to checkpatch
61        """
62        base = '''From 125b77450f4c66b8fd9654319520bbe795c9ef31 Mon Sep 17 00:00:00 2001
63From: Simon Glass <sjg@chromium.org>
64Date: Sun, 14 Jun 2020 09:45:14 -0600
65Subject: [PATCH] Test commit
66
67This is a test commit.
68
69Signed-off-by: Simon Glass <sjg@chromium.org>
70---
71
72'''
73        lines = base.splitlines()
74
75        # Create the diffstat
76        change = 0
77        insert = 0
78        for line in self.lines:
79            lines.append(' %s      | 1 +' % line.fname)
80            change += 1
81            insert += 1
82        lines.append(' %d files changed, %d insertions(+)' % (change, insert))
83        lines.append('')
84
85        # Create the patch info for each file
86        for line in self.lines:
87            lines.append('diff --git a/%s b/%s' % (line.fname, line.fname))
88            lines.append('index 7837d459f18..5ba7840f68e 100644')
89            lines.append('--- a/%s' % line.fname)
90            lines.append('+++ b/%s' % line.fname)
91            lines += ('''@@ -121,6 +121,7 @@ enum uclass_id {
92 	UCLASS_W1,		/* Dallas 1-Wire bus */
93 	UCLASS_W1_EEPROM,	/* one-wire EEPROMs */
94 	UCLASS_WDT,		/* Watchdog Timer driver */
95+%s
96
97 	UCLASS_COUNT,
98 	UCLASS_INVALID = -1,
99''' % line.text).splitlines()
100        lines.append('---')
101        lines.append('2.17.1')
102
103        return '\n'.join(lines)
104
105    def get_patch(self):
106        """Get the patch text and write it into a temporary file
107
108        Returns:
109            str: Filename containing the patch
110        """
111        inhandle, inname = tempfile.mkstemp()
112        infd = os.fdopen(inhandle, 'w')
113        infd.write(self.get_patch_text())
114        infd.close()
115        return inname
116
117    def run_checkpatch(self):
118        """Run checkpatch on the patch file
119
120        Returns:
121            namedtuple containing:
122                ok: False=failure, True=ok
123                problems: List of problems, each a dict:
124                    'type'; error or warning
125                    'msg': text message
126                    'file' : filename
127                    'line': line number
128                errors: Number of errors
129                warnings: Number of warnings
130                checks: Number of checks
131                lines: Number of lines
132                stdout: Full output of checkpatch
133        """
134        return checkpatch.check_patch(self.get_patch(), show_types=True)
135
136
137class TestPatch(unittest.TestCase):
138    """Test the u_boot_line() function in checkpatch.pl"""
139
140    def test_basic(self):
141        """Test basic filter operation"""
142        data='''
143
144From 656c9a8c31fa65859d924cd21da920d6ba537fad Mon Sep 17 00:00:00 2001
145From: Simon Glass <sjg@chromium.org>
146Date: Thu, 28 Apr 2011 09:58:51 -0700
147Subject: [PATCH (resend) 3/7] Tegra2: Add more clock support
148
149This adds functions to enable/disable clocks and reset to on-chip peripherals.
150
151cmd/pci.c:152:11: warning: format ���%llx��� expects argument of type
152   ���long long unsigned int���, but argument 3 has type
153   ���u64 {aka long unsigned int}��� [-Wformat=]
154
155BUG=chromium-os:13875
156TEST=build U-Boot for Seaboard, boot
157
158Change-Id: I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413
159
160Review URL: http://codereview.chromium.org/6900006
161
162Signed-off-by: Simon Glass <sjg@chromium.org>
163---
164 arch/arm/cpu/armv7/tegra2/Makefile         |    2 +-
165 arch/arm/cpu/armv7/tegra2/ap20.c           |   57 ++----
166 arch/arm/cpu/armv7/tegra2/clock.c          |  163 +++++++++++++++++
167'''
168        expected='''Message-Id: <19991231235959.0.I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413@changeid>
169
170
171From 656c9a8c31fa65859d924cd21da920d6ba537fad Mon Sep 17 00:00:00 2001
172From: Simon Glass <sjg@chromium.org>
173Date: Thu, 28 Apr 2011 09:58:51 -0700
174Subject: [PATCH (resend) 3/7] Tegra2: Add more clock support
175
176This adds functions to enable/disable clocks and reset to on-chip peripherals.
177
178cmd/pci.c:152:11: warning: format ���%llx��� expects argument of type
179   ���long long unsigned int���, but argument 3 has type
180   ���u64 {aka long unsigned int}��� [-Wformat=]
181
182Signed-off-by: Simon Glass <sjg@chromium.org>
183---
184
185 arch/arm/cpu/armv7/tegra2/Makefile         |    2 +-
186 arch/arm/cpu/armv7/tegra2/ap20.c           |   57 ++----
187 arch/arm/cpu/armv7/tegra2/clock.c          |  163 +++++++++++++++++
188'''
189        out = ''
190        inhandle, inname = tempfile.mkstemp()
191        infd = os.fdopen(inhandle, 'w', encoding='utf-8')
192        infd.write(data)
193        infd.close()
194
195        exphandle, expname = tempfile.mkstemp()
196        expfd = os.fdopen(exphandle, 'w', encoding='utf-8')
197        expfd.write(expected)
198        expfd.close()
199
200        # Normally by the time we call fix_patch we've already collected
201        # metadata.  Here, we haven't, but at least fake up something.
202        # Set the "count" to -1 which tells fix_patch to use a bogus/fixed
203        # time for generating the Message-Id.
204        com = commit.Commit('')
205        com.change_id = 'I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413'
206        com.count = -1
207
208        patchstream.fix_patch(None, inname, series.Series(), com)
209
210        rc = os.system('diff -u %s %s' % (inname, expname))
211        self.assertEqual(rc, 0)
212        os.remove(inname)
213
214        # Test whether the keep_change_id settings works.
215        inhandle, inname = tempfile.mkstemp()
216        infd = os.fdopen(inhandle, 'w', encoding='utf-8')
217        infd.write(data)
218        infd.close()
219
220        patchstream.fix_patch(None, inname, series.Series(), com,
221                              keep_change_id=True)
222
223        with open(inname, 'r') as f:
224            content = f.read()
225            self.assertIn(
226                'Change-Id: I80fe1d0c0b7dd10aa58ce5bb1d9290b6664d5413',
227                content)
228
229        os.remove(inname)
230        os.remove(expname)
231
232    def get_data(self, data_type):
233        data='''From 4924887af52713cabea78420eff03badea8f0035 Mon Sep 17 00:00:00 2001
234From: Simon Glass <sjg@chromium.org>
235Date: Thu, 7 Apr 2011 10:14:41 -0700
236Subject: [PATCH 1/4] Add microsecond boot time measurement
237
238This defines the basics of a new boot time measurement feature. This allows
239logging of very accurate time measurements as the boot proceeds, by using
240an available microsecond counter.
241
242%s
243---
244 README              |   11 ++++++++
245 MAINTAINERS         |    3 ++
246 common/bootstage.c  |   50 ++++++++++++++++++++++++++++++++++++
247 include/bootstage.h |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++
248 include/common.h    |    8 ++++++
249 5 files changed, 141 insertions(+), 0 deletions(-)
250 create mode 100644 common/bootstage.c
251 create mode 100644 include/bootstage.h
252
253diff --git a/README b/README
254index 6f3748d..f9e4e65 100644
255--- a/README
256+++ b/README
257@@ -2026,6 +2026,17 @@ The following options need to be configured:
258 		example, some LED's) on your board. At the moment,
259 		the following checkpoints are implemented:
260
261+- Time boot progress
262+		CONFIG_BOOTSTAGE
263+
264+		Define this option to enable microsecond boot stage timing
265+		on supported platforms. For this to work your platform
266+		needs to define a function timer_get_us() which returns the
267+		number of microseconds since reset. This would normally
268+		be done in your SOC or board timer.c file.
269+
270+		You can add calls to bootstage_mark() to set time markers.
271+
272 - Standalone program support:
273 		CONFIG_STANDALONE_LOAD_ADDR
274
275diff --git a/MAINTAINERS b/MAINTAINERS
276index b167b028ec..beb7dc634f 100644
277--- a/MAINTAINERS
278+++ b/MAINTAINERS
279@@ -474,3 +474,8 @@ S:	Maintained
280 T:	git git://git.denx.de/u-boot.git
281 F:	*
282 F:	*/
283+
284+BOOTSTAGE
285+M:	Simon Glass <sjg@chromium.org>
286+L:	u-boot@lists.denx.de
287+F:	common/bootstage.c
288diff --git a/common/bootstage.c b/common/bootstage.c
289new file mode 100644
290index 0000000..2234c87
291--- /dev/null
292+++ b/common/bootstage.c
293@@ -0,0 +1,37 @@
294+%s
295+/*
296+ * Copyright (c) 2011, Google Inc. All rights reserved.
297+ *
298+ */
299+
300+/*
301+ * This module records the progress of boot and arbitrary commands, and
302+ * permits accurate timestamping of each. The records can optionally be
303+ * passed to kernel in the ATAGs
304+ */
305+
306+#include <config.h>
307+
308+struct bootstage_record {
309+	u32 time_us;
310+	const char *name;
311+};
312+
313+static struct bootstage_record record[BOOTSTAGE_COUNT];
314+
315+u32 bootstage_mark(enum bootstage_id id, const char *name)
316+{
317+	struct bootstage_record *rec = &record[id];
318+
319+	/* Only record the first event for each */
320+%sif (!rec->name) {
321+		rec->time_us = (u32)timer_get_us();
322+		rec->name = name;
323+	}
324+	if (!rec->name &&
325+	%ssomething_else) {
326+		rec->time_us = (u32)timer_get_us();
327+		rec->name = name;
328+	}
329+%sreturn rec->time_us;
330+}
331--
3321.7.3.1
333'''
334        signoff = 'Signed-off-by: Simon Glass <sjg@chromium.org>\n'
335        license = '// SPDX-License-Identifier: GPL-2.0+'
336        tab = '	'
337        indent = '    '
338        if data_type == 'good':
339            pass
340        elif data_type == 'no-signoff':
341            signoff = ''
342        elif data_type == 'no-license':
343            license = ''
344        elif data_type == 'spaces':
345            tab = '   '
346        elif data_type == 'indent':
347            indent = tab
348        else:
349            print('not implemented')
350        return data % (signoff, license, tab, indent, tab)
351
352    def setup_data(self, data_type):
353        inhandle, inname = tempfile.mkstemp()
354        infd = os.fdopen(inhandle, 'w')
355        data = self.get_data(data_type)
356        infd.write(data)
357        infd.close()
358        return inname
359
360    def test_good(self):
361        """Test checkpatch operation"""
362        inf = self.setup_data('good')
363        result = checkpatch.check_patch(inf)
364        self.assertEqual(result.ok, True)
365        self.assertEqual(result.problems, [])
366        self.assertEqual(result.errors, 0)
367        self.assertEqual(result.warnings, 0)
368        self.assertEqual(result.checks, 0)
369        self.assertEqual(result.lines, 62)
370        os.remove(inf)
371
372    def test_no_signoff(self):
373        inf = self.setup_data('no-signoff')
374        result = checkpatch.check_patch(inf)
375        self.assertEqual(result.ok, False)
376        self.assertEqual(len(result.problems), 1)
377        self.assertEqual(result.errors, 1)
378        self.assertEqual(result.warnings, 0)
379        self.assertEqual(result.checks, 0)
380        self.assertEqual(result.lines, 62)
381        os.remove(inf)
382
383    def test_no_license(self):
384        inf = self.setup_data('no-license')
385        result = checkpatch.check_patch(inf)
386        self.assertEqual(result.ok, False)
387        self.assertEqual(len(result.problems), 1)
388        self.assertEqual(result.errors, 0)
389        self.assertEqual(result.warnings, 1)
390        self.assertEqual(result.checks, 0)
391        self.assertEqual(result.lines, 62)
392        os.remove(inf)
393
394    def test_spaces(self):
395        inf = self.setup_data('spaces')
396        result = checkpatch.check_patch(inf)
397        self.assertEqual(result.ok, False)
398        self.assertEqual(len(result.problems), 3)
399        self.assertEqual(result.errors, 0)
400        self.assertEqual(result.warnings, 3)
401        self.assertEqual(result.checks, 0)
402        self.assertEqual(result.lines, 62)
403        os.remove(inf)
404
405    def test_indent(self):
406        inf = self.setup_data('indent')
407        result = checkpatch.check_patch(inf)
408        self.assertEqual(result.ok, False)
409        self.assertEqual(len(result.problems), 1)
410        self.assertEqual(result.errors, 0)
411        self.assertEqual(result.warnings, 0)
412        self.assertEqual(result.checks, 1)
413        self.assertEqual(result.lines, 62)
414        os.remove(inf)
415
416    def check_single_message(self, pm, msg, pmtype = 'warning'):
417        """Helper function to run checkpatch and check the result
418
419        Args:
420            pm: PatchMaker object to use
421            msg: Expected message (e.g. 'LIVETREE')
422            pmtype: Type of problem ('error', 'warning')
423        """
424        result = pm.run_checkpatch()
425        if pmtype == 'warning':
426            self.assertEqual(result.warnings, 1)
427        elif pmtype == 'error':
428            self.assertEqual(result.errors, 1)
429        if len(result.problems) != 1:
430            print(result.problems)
431        self.assertEqual(len(result.problems), 1)
432        self.assertIn(msg, result.problems[0]['cptype'])
433
434    def test_uclass(self):
435        """Test for possible new uclass"""
436        pm = PatchMaker()
437        pm.add_line('include/dm/uclass-id.h', 'UCLASS_WIBBLE,')
438        self.check_single_message(pm, 'NEW_UCLASS')
439
440    def test_livetree(self):
441        """Test for using the livetree API"""
442        pm = PatchMaker()
443        pm.add_line('common/main.c', 'fdtdec_do_something()')
444        self.check_single_message(pm, 'LIVETREE')
445
446    def test_new_command(self):
447        """Test for adding a new command"""
448        pm = PatchMaker()
449        pm.add_line('common/main.c', 'do_wibble(struct cmd_tbl *cmd_tbl)')
450        self.check_single_message(pm, 'CMD_TEST')
451
452    def test_prefer_if(self):
453        """Test for using #ifdef"""
454        pm = PatchMaker()
455        pm.add_line('common/main.c', '#ifdef CONFIG_YELLOW')
456        pm.add_line('common/init.h', '#ifdef CONFIG_YELLOW')
457        pm.add_line('fred.dtsi', '#ifdef CONFIG_YELLOW')
458        self.check_single_message(pm, "PREFER_IF")
459
460    def test_command_use_defconfig(self):
461        """Test for enabling/disabling commands using preprocesor"""
462        pm = PatchMaker()
463        pm.add_line('common/main.c', '#undef CONFIG_CMD_WHICH')
464        self.check_single_message(pm, 'DEFINE_CONFIG_SYM', 'error')
465
466    def test_barred_include_in_hdr(self):
467        """Test for using a barred include in a header file"""
468        pm = PatchMaker()
469        pm.add_line('include/myfile.h', '#include <dm.h>')
470        self.check_single_message(pm, 'BARRED_INCLUDE_IN_HDR', 'error')
471
472    def test_barred_include_common_h(self):
473        """Test for adding common.h to a file"""
474        pm = PatchMaker()
475        pm.add_line('include/myfile.h', '#include <common.h>')
476        self.check_single_message(pm, 'BARRED_INCLUDE_COMMON_H', 'error')
477
478    def test_config_is_enabled_config(self):
479        """Test for accidental CONFIG_IS_ENABLED(CONFIG_*) calls"""
480        pm = PatchMaker()
481        pm.add_line('common/main.c', 'if (CONFIG_IS_ENABLED(CONFIG_CLK))')
482        self.check_single_message(pm, 'CONFIG_IS_ENABLED_CONFIG', 'error')
483
484    def check_struct(self, auto, suffix, warning):
485        """Check one of the warnings for struct naming
486
487        Args:
488            auto: Auto variable name, e.g. 'per_child_auto'
489            suffix: Suffix to expect on member, e.g. '_priv'
490            warning: Warning name, e.g. 'PRIV_AUTO'
491        """
492        pm = PatchMaker()
493        pm.add_line('common/main.c', '.%s = sizeof(struct(fred)),' % auto)
494        pm.add_line('common/main.c', '.%s = sizeof(struct(mary%s)),' %
495                    (auto, suffix))
496        self.check_single_message(
497            pm, warning, "struct 'fred' should have a %s suffix" % suffix)
498
499    def test_dm_driver_auto(self):
500        """Check for the correct suffix on 'struct driver' auto members"""
501        self.check_struct('priv_auto', '_priv', 'PRIV_AUTO')
502        self.check_struct('plat_auto', '_plat', 'PLAT_AUTO')
503        self.check_struct('per_child_auto', '_priv', 'CHILD_PRIV_AUTO')
504        self.check_struct('per_child_plat_auto', '_plat', 'CHILD_PLAT_AUTO')
505
506    def test_dm_uclass_auto(self):
507        """Check for the correct suffix on 'struct uclass' auto members"""
508        # Some of these are omitted since they match those from struct driver
509        self.check_struct('per_device_auto', '_priv', 'DEVICE_PRIV_AUTO')
510        self.check_struct('per_device_plat_auto', '_plat', 'DEVICE_PLAT_AUTO')
511
512    def check_strl(self, func):
513        """Check one of the checks for strn(cpy|cat)"""
514        pm = PatchMaker()
515        pm.add_line('common/main.c', "strn%s(foo, bar, sizeof(foo));" % func)
516        self.check_single_message(pm, "STRL",
517            "strl%s is preferred over strn%s because it always produces a nul-terminated string\n"
518            % (func, func))
519
520    def test_strl(self):
521        """Check for uses of strn(cat|cpy)"""
522        self.check_strl("cat");
523        self.check_strl("cpy");
524
525    def test_schema(self):
526        """Check for uses of strn(cat|cpy)"""
527        pm = PatchMaker()
528        pm.add_line('arch/sandbox/dts/sandbox.dtsi', '\tu-boot,dm-pre-proper;')
529        self.check_single_message(pm, 'PRE_SCHEMA', 'error')
530
531if __name__ == "__main__":
532    unittest.main()
533    gitutil.RunTests()
534