1# Functional test that boots known good tuxboot images the same way
2# that tuxrun (www.tuxrun.org) does. This tool is used by things like
3# the LKFT project to run regression tests on kernels.
4#
5# Copyright (c) 2023 Linaro Ltd.
6#
7# Author:
8#  Alex Bennée <alex.bennee@linaro.org>
9#
10# SPDX-License-Identifier: GPL-2.0-or-later
11
12import os
13import time
14
15from avocado import skip, skipIf
16from avocado_qemu import QemuSystemTest
17from avocado_qemu import exec_command, exec_command_and_wait_for_pattern
18from avocado_qemu import wait_for_console_pattern
19from avocado.utils import process
20from avocado.utils.path import find_command
21
22class TuxRunBaselineTest(QemuSystemTest):
23    """
24    :avocado: tags=accel:tcg
25    """
26
27    KERNEL_COMMON_COMMAND_LINE = 'printk.time=0'
28    # Tests are ~10-40s, allow for --debug/--enable-gcov overhead
29    timeout = 100
30
31    def get_tag(self, tagname, default=None):
32        """
33        Get the metadata tag or return the default.
34        """
35        utag = self._get_unique_tag_val(tagname)
36        print(f"{tagname}/{default} -> {utag}")
37        if utag:
38            return utag
39
40        return default
41
42    def setUp(self):
43        super().setUp()
44
45        # We need zstd for all the tuxrun tests
46        # See https://github.com/avocado-framework/avocado/issues/5609
47        zstd = find_command('zstd', False)
48        if zstd is False:
49            self.cancel('Could not find "zstd", which is required to '
50                        'decompress rootfs')
51        self.zstd = zstd
52
53        # Process the TuxRun specific tags, most machines work with
54        # reasonable defaults but we sometimes need to tweak the
55        # config. To avoid open coding everything we store all these
56        # details in the metadata for each test.
57
58        # The tuxboot tag matches the root directory
59        self.tuxboot = self.get_tag('tuxboot')
60
61        # Most Linux's use ttyS0 for their serial port
62        self.console = self.get_tag('console', "ttyS0")
63
64        # Does the machine shutdown QEMU nicely on "halt"
65        self.shutdown = self.get_tag('shutdown')
66
67        # The name of the kernel Image file
68        self.image = self.get_tag('image', "Image")
69
70        # The block device drive type
71        self.drive = self.get_tag('drive', "virtio-blk-device")
72
73        self.root = self.get_tag('root', "vda")
74
75        # Occasionally we need extra devices to hook things up
76        self.extradev = self.get_tag('extradev')
77
78    def wait_for_console_pattern(self, success_message, vm=None):
79        wait_for_console_pattern(self, success_message,
80                                 failure_message='Kernel panic - not syncing',
81                                 vm=vm)
82
83    def fetch_tuxrun_assets(self, dt=None):
84        """
85        Fetch the TuxBoot assets. They are stored in a standard way so we
86        use the per-test tags to fetch details.
87        """
88        base_url = f"https://storage.tuxboot.com/{self.tuxboot}/"
89        kernel_image =  self.fetch_asset(base_url + self.image)
90        disk_image_zst = self.fetch_asset(base_url + "rootfs.ext4.zst")
91
92        cmd = f"{self.zstd} -d {disk_image_zst} -o {self.workdir}/rootfs.ext4"
93        process.run(cmd)
94
95        if dt:
96            dtb = self.fetch_asset(base_url + dt)
97        else:
98            dtb = None
99
100        return (kernel_image, self.workdir + "/rootfs.ext4", dtb)
101
102    def prepare_run(self, kernel, disk, dtb=None, console_index=0):
103        """
104        Setup to run and add the common parameters to the system
105        """
106        self.vm.set_console(console_index=console_index)
107
108        # all block devices are raw ext4's
109        blockdev = "driver=raw,file.driver=file," \
110            + f"file.filename={disk},node-name=hd0"
111
112        kcmd_line = self.KERNEL_COMMON_COMMAND_LINE
113        kcmd_line += f" root=/dev/{self.root}"
114        kcmd_line += f" console={self.console}"
115
116        self.vm.add_args('-kernel', kernel,
117                         '-append', kcmd_line,
118                         '-blockdev', blockdev)
119
120        # Sometimes we need extra devices attached
121        if self.extradev:
122            self.vm.add_args('-device', self.extradev)
123
124        # Some machines already define a drive device
125        if self.drive != "none":
126            self.vm.add_args('-device',
127                             f"{self.drive},drive=hd0")
128
129        # Some machines need an explicit DTB
130        if dtb:
131            self.vm.add_args('-dtb', dtb)
132
133    def run_tuxtest_tests(self, haltmsg):
134        """
135        Wait for the system to boot up, wait for the login prompt and
136        then do a few things on the console. Trigger a shutdown and
137        wait to exit cleanly.
138        """
139        self.wait_for_console_pattern("Welcome to TuxTest")
140        time.sleep(0.2)
141        exec_command(self, 'root')
142        time.sleep(0.2)
143        exec_command(self, 'cat /proc/interrupts')
144        time.sleep(0.1)
145        exec_command(self, 'cat /proc/self/maps')
146        time.sleep(0.1)
147        exec_command(self, 'uname -a')
148        time.sleep(0.1)
149        exec_command_and_wait_for_pattern(self, 'halt', haltmsg)
150
151        # Wait for VM to shut down gracefully if it can
152        if self.shutdown == "nowait":
153            self.vm.shutdown()
154        else:
155            self.vm.wait()
156
157    def common_tuxrun(self, dt=None, haltmsg="reboot: System halted",
158                      console_index=0):
159        """
160        Common path for LKFT tests. Unless we need to do something
161        special with the command line we can process most things using
162        the tag metadata.
163        """
164        (kernel, disk, dtb) = self.fetch_tuxrun_assets(dt)
165
166        self.prepare_run(kernel, disk, dtb, console_index)
167        self.vm.launch()
168        self.run_tuxtest_tests(haltmsg)
169
170    #
171    # The tests themselves. The configuration is derived from how
172    # tuxrun invokes qemu (with minor tweaks like using -blockdev
173    # consistently). The tuxrun equivalent is something like:
174    #
175    # tuxrun --device qemu-{ARCH} \
176    #        --kernel https://storage.tuxboot.com/{TUXBOOT}/{IMAGE}
177    #
178
179    def test_arm64(self):
180        """
181        :avocado: tags=arch:aarch64
182        :avocado: tags=cpu:cortex-a57
183        :avocado: tags=machine:virt
184        :avocado: tags=tuxboot:arm64
185        :avocado: tags=console:ttyAMA0
186        :avocado: tags=shutdown:nowait
187        """
188        self.common_tuxrun()
189
190    def test_arm64be(self):
191        """
192        :avocado: tags=arch:aarch64
193        :avocado: tags=cpu:cortex-a57
194        :avocado: tags=endian:big
195        :avocado: tags=machine:virt
196        :avocado: tags=tuxboot:arm64be
197        :avocado: tags=console:ttyAMA0
198        :avocado: tags=shutdown:nowait
199        """
200        self.common_tuxrun()
201
202    def test_armv5(self):
203        """
204        :avocado: tags=arch:arm
205        :avocado: tags=cpu:arm926
206        :avocado: tags=machine:versatilepb
207        :avocado: tags=tuxboot:armv5
208        :avocado: tags=image:zImage
209        :avocado: tags=drive:virtio-blk-pci
210        :avocado: tags=console:ttyAMA0
211        :avocado: tags=shutdown:nowait
212        """
213        self.common_tuxrun(dt="versatile-pb.dtb")
214
215    def test_armv7(self):
216        """
217        :avocado: tags=arch:arm
218        :avocado: tags=cpu:cortex-a15
219        :avocado: tags=machine:virt
220        :avocado: tags=tuxboot:armv7
221        :avocado: tags=image:zImage
222        :avocado: tags=console:ttyAMA0
223        :avocado: tags=shutdown:nowait
224        """
225        self.common_tuxrun()
226
227    def test_armv7be(self):
228        """
229        :avocado: tags=arch:arm
230        :avocado: tags=cpu:cortex-a15
231        :avocado: tags=endian:big
232        :avocado: tags=machine:virt
233        :avocado: tags=tuxboot:armv7be
234        :avocado: tags=image:zImage
235        :avocado: tags=console:ttyAMA0
236        :avocado: tags=shutdown:nowait
237        """
238        self.common_tuxrun()
239
240    def test_i386(self):
241        """
242        :avocado: tags=arch:i386
243        :avocado: tags=cpu:coreduo
244        :avocado: tags=machine:q35
245        :avocado: tags=tuxboot:i386
246        :avocado: tags=image:bzImage
247        :avocado: tags=drive:virtio-blk-pci
248        :avocado: tags=shutdown:nowait
249        """
250        self.common_tuxrun()
251
252    def test_mips32(self):
253        """
254        :avocado: tags=arch:mips
255        :avocado: tags=machine:malta
256        :avocado: tags=cpu:mips32r6-generic
257        :avocado: tags=endian:big
258        :avocado: tags=tuxboot:mips32
259        :avocado: tags=image:vmlinux
260        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
261        :avocado: tags=root:sda
262        :avocado: tags=shutdown:nowait
263        """
264        self.common_tuxrun()
265
266    def test_mips32el(self):
267        """
268        :avocado: tags=arch:mipsel
269        :avocado: tags=machine:malta
270        :avocado: tags=cpu:mips32r6-generic
271        :avocado: tags=tuxboot:mips32el
272        :avocado: tags=image:vmlinux
273        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
274        :avocado: tags=root:sda
275        :avocado: tags=shutdown:nowait
276        """
277        self.common_tuxrun()
278
279    @skip("QEMU currently broken") # regression against stable QEMU
280    def test_mips64(self):
281        """
282        :avocado: tags=arch:mips64
283        :avocado: tags=machine:malta
284        :avocado: tags=tuxboot:mips64
285        :avocado: tags=endian:big
286        :avocado: tags=image:vmlinux
287        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
288        :avocado: tags=root:sda
289        :avocado: tags=shutdown:nowait
290        """
291        self.common_tuxrun()
292
293    def test_mips64el(self):
294        """
295        :avocado: tags=arch:mips64el
296        :avocado: tags=machine:malta
297        :avocado: tags=tuxboot:mips64el
298        :avocado: tags=image:vmlinux
299        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
300        :avocado: tags=root:sda
301        :avocado: tags=shutdown:nowait
302        """
303        self.common_tuxrun()
304
305    def test_ppc32(self):
306        """
307        :avocado: tags=arch:ppc
308        :avocado: tags=machine:ppce500
309        :avocado: tags=cpu:e500mc
310        :avocado: tags=tuxboot:ppc32
311        :avocado: tags=image:uImage
312        :avocado: tags=drive:virtio-blk-pci
313        :avocado: tags=shutdown:nowait
314        """
315        self.common_tuxrun()
316
317    def test_ppc64(self):
318        """
319        :avocado: tags=arch:ppc64
320        :avocado: tags=machine:pseries
321        :avocado: tags=cpu:POWER8
322        :avocado: tags=endian:big
323        :avocado: tags=console:hvc0
324        :avocado: tags=tuxboot:ppc64
325        :avocado: tags=image:vmlinux
326        :avocado: tags=extradev:driver=spapr-vscsi
327        :avocado: tags=drive:scsi-hd
328        :avocado: tags=root:sda
329        """
330        self.common_tuxrun()
331
332    def test_ppc64le(self):
333        """
334        :avocado: tags=arch:ppc64
335        :avocado: tags=machine:pseries
336        :avocado: tags=cpu:POWER8
337        :avocado: tags=console:hvc0
338        :avocado: tags=tuxboot:ppc64le
339        :avocado: tags=image:vmlinux
340        :avocado: tags=extradev:driver=spapr-vscsi
341        :avocado: tags=drive:scsi-hd
342        :avocado: tags=root:sda
343        """
344        self.common_tuxrun()
345
346    def test_riscv32(self):
347        """
348        :avocado: tags=arch:riscv32
349        :avocado: tags=machine:virt
350        :avocado: tags=tuxboot:riscv32
351        """
352        self.common_tuxrun()
353
354    def test_riscv64(self):
355        """
356        :avocado: tags=arch:riscv64
357        :avocado: tags=machine:virt
358        :avocado: tags=tuxboot:riscv64
359        """
360        self.common_tuxrun()
361
362    def test_s390(self):
363        """
364        :avocado: tags=arch:s390x
365        :avocado: tags=endian:big
366        :avocado: tags=tuxboot:s390
367        :avocado: tags=image:bzImage
368        :avocado: tags=drive:virtio-blk-ccw
369        :avocado: tags=shutdown:nowait
370        """
371        self.common_tuxrun(haltmsg="Requesting system halt")
372
373    # Note: some segfaults caused by unaligned userspace access
374    @skipIf(os.getenv('GITLAB_CI'), 'Skipping unstable test on GitLab')
375    def test_sh4(self):
376        """
377        :avocado: tags=arch:sh4
378        :avocado: tags=machine:r2d
379        :avocado: tags=cpu:sh7785
380        :avocado: tags=tuxboot:sh4
381        :avocado: tags=image:zImage
382        :avocado: tags=root:sda
383        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
384        :avocado: tags=console:ttySC1
385        """
386        # The test is currently too unstable to do much in userspace
387        # so we skip common_tuxrun and do a minimal boot and shutdown.
388        (kernel, disk, dtb) = self.fetch_tuxrun_assets()
389
390        # the console comes on the second serial port
391        self.prepare_run(kernel, disk, console_index=1)
392        self.vm.launch()
393
394        self.wait_for_console_pattern("Welcome to TuxTest")
395        time.sleep(0.1)
396        exec_command(self, 'root')
397        time.sleep(0.1)
398        exec_command_and_wait_for_pattern(self, 'halt',
399                                          "reboot: System halted")
400
401    def test_sparc64(self):
402        """
403        :avocado: tags=arch:sparc64
404        :avocado: tags=tuxboot:sparc64
405        :avocado: tags=image:vmlinux
406        :avocado: tags=root:sda
407        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
408        :avocado: tags=shutdown:nowait
409        """
410        self.common_tuxrun()
411
412    def test_x86_64(self):
413        """
414        :avocado: tags=arch:x86_64
415        :avocado: tags=machine:q35
416        :avocado: tags=cpu:Nehalem
417        :avocado: tags=tuxboot:x86_64
418        :avocado: tags=image:bzImage
419        :avocado: tags=root:sda
420        :avocado: tags=drive:driver=ide-hd,bus=ide.0,unit=0
421        :avocado: tags=shutdown:nowait
422        """
423        self.common_tuxrun()
424