xref: /openbmc/u-boot/test/py/tests/test_fs/conftest.py (revision 04b24965)
1# SPDX-License-Identifier:      GPL-2.0+
2# Copyright (c) 2018, Linaro Limited
3# Author: Takahiro Akashi <takahiro.akashi@linaro.org>
4
5import os
6import os.path
7import pytest
8import re
9from subprocess import call, check_call, check_output, CalledProcessError
10from fstest_defs import *
11
12supported_fs_basic = ['fat16', 'fat32', 'ext4']
13supported_fs_ext = ['fat16', 'fat32']
14supported_fs_mkdir = ['fat16', 'fat32']
15supported_fs_unlink = ['fat16', 'fat32']
16
17#
18# Filesystem test specific setup
19#
20def pytest_addoption(parser):
21    """Enable --fs-type option.
22
23    See pytest_configure() about how it works.
24
25    Args:
26        parser: Pytest command-line parser.
27
28    Returns:
29        Nothing.
30    """
31    parser.addoption('--fs-type', action='append', default=None,
32        help='Targeting Filesystem Types')
33
34def pytest_configure(config):
35    """Restrict a file system(s) to be tested.
36
37    A file system explicitly named with --fs-type option is selected
38    if it belongs to a default supported_fs_xxx list.
39    Multiple options can be specified.
40
41    Args:
42        config: Pytest configuration.
43
44    Returns:
45        Nothing.
46    """
47    global supported_fs_basic
48    global supported_fs_ext
49    global supported_fs_mkdir
50    global supported_fs_unlink
51
52    def intersect(listA, listB):
53        return  [x for x in listA if x in listB]
54
55    supported_fs = config.getoption('fs_type')
56    if supported_fs:
57        print('*** FS TYPE modified: %s' % supported_fs)
58        supported_fs_basic =  intersect(supported_fs, supported_fs_basic)
59        supported_fs_ext =  intersect(supported_fs, supported_fs_ext)
60        supported_fs_mkdir =  intersect(supported_fs, supported_fs_mkdir)
61        supported_fs_unlink =  intersect(supported_fs, supported_fs_unlink)
62
63def pytest_generate_tests(metafunc):
64    """Parametrize fixtures, fs_obj_xxx
65
66    Each fixture will be parametrized with a corresponding support_fs_xxx
67    list.
68
69    Args:
70        metafunc: Pytest test function.
71
72    Returns:
73        Nothing.
74    """
75    if 'fs_obj_basic' in metafunc.fixturenames:
76        metafunc.parametrize('fs_obj_basic', supported_fs_basic,
77            indirect=True, scope='module')
78    if 'fs_obj_ext' in metafunc.fixturenames:
79        metafunc.parametrize('fs_obj_ext', supported_fs_ext,
80            indirect=True, scope='module')
81    if 'fs_obj_mkdir' in metafunc.fixturenames:
82        metafunc.parametrize('fs_obj_mkdir', supported_fs_mkdir,
83            indirect=True, scope='module')
84    if 'fs_obj_unlink' in metafunc.fixturenames:
85        metafunc.parametrize('fs_obj_unlink', supported_fs_unlink,
86            indirect=True, scope='module')
87
88#
89# Helper functions
90#
91def fstype_to_ubname(fs_type):
92    """Convert a file system type to an U-boot specific string
93
94    A generated string can be used as part of file system related commands
95    or a config name in u-boot. Currently fat16 and fat32 are handled
96    specifically.
97
98    Args:
99        fs_type: File system type.
100
101    Return:
102        A corresponding string for file system type.
103    """
104    if re.match('fat', fs_type):
105        return 'fat'
106    else:
107        return fs_type
108
109def check_ubconfig(config, fs_type):
110    """Check whether a file system is enabled in u-boot configuration.
111
112    This function is assumed to be called in a fixture function so that
113    the whole test cases will be skipped if a given file system is not
114    enabled.
115
116    Args:
117        fs_type: File system type.
118
119    Return:
120        Nothing.
121    """
122    if not config.buildconfig.get('config_cmd_%s' % fs_type, None):
123        pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper())
124    if not config.buildconfig.get('config_%s_write' % fs_type, None):
125        pytest.skip('.config feature "%s_WRITE" not enabled'
126        % fs_type.upper())
127
128def mk_fs(config, fs_type, size, id):
129    """Create a file system volume.
130
131    Args:
132        fs_type: File system type.
133        size: Size of file system in MiB.
134        id: Prefix string of volume's file name.
135
136    Return:
137        Nothing.
138    """
139    fs_img = '%s.%s.img' % (id, fs_type)
140    fs_img = config.persistent_data_dir + '/' + fs_img
141
142    if fs_type == 'fat16':
143        mkfs_opt = '-F 16'
144    elif fs_type == 'fat32':
145        mkfs_opt = '-F 32'
146    else:
147        mkfs_opt = ''
148
149    if re.match('fat', fs_type):
150        fs_lnxtype = 'vfat'
151    else:
152        fs_lnxtype = fs_type
153
154    count = (size + 1048576 - 1) / 1048576
155
156    try:
157        check_call('rm -f %s' % fs_img, shell=True)
158        check_call('dd if=/dev/zero of=%s bs=1M count=%d'
159            % (fs_img, count), shell=True)
160        check_call('mkfs.%s %s %s'
161            % (fs_lnxtype, mkfs_opt, fs_img), shell=True)
162        return fs_img
163    except CalledProcessError:
164        call('rm -f %s' % fs_img, shell=True)
165        raise
166
167# from test/py/conftest.py
168def tool_is_in_path(tool):
169    """Check whether a given command is available on host.
170
171    Args:
172        tool: Command name.
173
174    Return:
175        True if available, False if not.
176    """
177    for path in os.environ['PATH'].split(os.pathsep):
178        fn = os.path.join(path, tool)
179        if os.path.isfile(fn) and os.access(fn, os.X_OK):
180            return True
181    return False
182
183fuse_mounted = False
184
185def mount_fs(fs_type, device, mount_point):
186    """Mount a volume.
187
188    Args:
189        fs_type: File system type.
190        device: Volume's file name.
191        mount_point: Mount point.
192
193    Return:
194        Nothing.
195    """
196    global fuse_mounted
197
198    fuse_mounted = False
199    try:
200        if tool_is_in_path('guestmount'):
201            fuse_mounted = True
202            check_call('guestmount -a %s -m /dev/sda %s'
203                % (device, mount_point), shell=True)
204        else:
205            mount_opt = 'loop,rw'
206            if re.match('fat', fs_type):
207                mount_opt += ',umask=0000'
208
209            check_call('sudo mount -o %s %s %s'
210                % (mount_opt, device, mount_point), shell=True)
211
212            # may not be effective for some file systems
213            check_call('sudo chmod a+rw %s' % mount_point, shell=True)
214    except CalledProcessError:
215        raise
216
217def umount_fs(mount_point):
218    """Unmount a volume.
219
220    Args:
221        mount_point: Mount point.
222
223    Return:
224        Nothing.
225    """
226    if fuse_mounted:
227        call('sync')
228        call('guestunmount %s' % mount_point, shell=True)
229    else:
230        call('sudo umount %s' % mount_point, shell=True)
231
232#
233# Fixture for basic fs test
234#     derived from test/fs/fs-test.sh
235#
236# NOTE: yield_fixture was deprecated since pytest-3.0
237@pytest.yield_fixture()
238def fs_obj_basic(request, u_boot_config):
239    """Set up a file system to be used in basic fs test.
240
241    Args:
242        request: Pytest request object.
243	u_boot_config: U-boot configuration.
244
245    Return:
246        A fixture for basic fs test, i.e. a triplet of file system type,
247        volume file name and  a list of MD5 hashes.
248    """
249    fs_type = request.param
250    fs_img = ''
251
252    fs_ubtype = fstype_to_ubname(fs_type)
253    check_ubconfig(u_boot_config, fs_ubtype)
254
255    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
256
257    small_file = mount_dir + '/' + SMALL_FILE
258    big_file = mount_dir + '/' + BIG_FILE
259
260    try:
261
262        # 3GiB volume
263        fs_img = mk_fs(u_boot_config, fs_type, 0xc0000000, '3GB')
264
265        # Mount the image so we can populate it.
266        check_call('mkdir -p %s' % mount_dir, shell=True)
267        mount_fs(fs_type, fs_img, mount_dir)
268
269        # Create a subdirectory.
270        check_call('mkdir %s/SUBDIR' % mount_dir, shell=True)
271
272        # Create big file in this image.
273        # Note that we work only on the start 1MB, couple MBs in the 2GB range
274        # and the last 1 MB of the huge 2.5GB file.
275        # So, just put random values only in those areas.
276        check_call('dd if=/dev/urandom of=%s bs=1M count=1'
277	    % big_file, shell=True)
278        check_call('dd if=/dev/urandom of=%s bs=1M count=2 seek=2047'
279            % big_file, shell=True)
280        check_call('dd if=/dev/urandom of=%s bs=1M count=1 seek=2499'
281            % big_file, shell=True)
282
283        # Create a small file in this image.
284        check_call('dd if=/dev/urandom of=%s bs=1M count=1'
285	    % small_file, shell=True)
286
287        # Delete the small file copies which possibly are written as part of a
288        # previous test.
289        # check_call('rm -f "%s.w"' % MB1, shell=True)
290        # check_call('rm -f "%s.w2"' % MB1, shell=True)
291
292        # Generate the md5sums of reads that we will test against small file
293        out = check_output(
294            'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
295	    % small_file, shell=True)
296        md5val = [ out.split()[0] ]
297
298        # Generate the md5sums of reads that we will test against big file
299        # One from beginning of file.
300        out = check_output(
301            'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
302	    % big_file, shell=True)
303        md5val.append(out.split()[0])
304
305        # One from end of file.
306        out = check_output(
307            'dd if=%s bs=1M skip=2499 count=1 2> /dev/null | md5sum'
308	    % big_file, shell=True)
309        md5val.append(out.split()[0])
310
311        # One from the last 1MB chunk of 2GB
312        out = check_output(
313            'dd if=%s bs=1M skip=2047 count=1 2> /dev/null | md5sum'
314	    % big_file, shell=True)
315        md5val.append(out.split()[0])
316
317        # One from the start 1MB chunk from 2GB
318        out = check_output(
319            'dd if=%s bs=1M skip=2048 count=1 2> /dev/null | md5sum'
320	    % big_file, shell=True)
321        md5val.append(out.split()[0])
322
323        # One 1MB chunk crossing the 2GB boundary
324        out = check_output(
325            'dd if=%s bs=512K skip=4095 count=2 2> /dev/null | md5sum'
326	    % big_file, shell=True)
327        md5val.append(out.split()[0])
328
329        umount_fs(mount_dir)
330    except CalledProcessError:
331        pytest.skip('Setup failed for filesystem: ' + fs_type)
332        return
333    else:
334        yield [fs_ubtype, fs_img, md5val]
335    finally:
336        umount_fs(mount_dir)
337        call('rmdir %s' % mount_dir, shell=True)
338        if fs_img:
339            call('rm -f %s' % fs_img, shell=True)
340
341#
342# Fixture for extended fs test
343#
344# NOTE: yield_fixture was deprecated since pytest-3.0
345@pytest.yield_fixture()
346def fs_obj_ext(request, u_boot_config):
347    """Set up a file system to be used in extended fs test.
348
349    Args:
350        request: Pytest request object.
351	u_boot_config: U-boot configuration.
352
353    Return:
354        A fixture for extended fs test, i.e. a triplet of file system type,
355        volume file name and  a list of MD5 hashes.
356    """
357    fs_type = request.param
358    fs_img = ''
359
360    fs_ubtype = fstype_to_ubname(fs_type)
361    check_ubconfig(u_boot_config, fs_ubtype)
362
363    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
364
365    min_file = mount_dir + '/' + MIN_FILE
366    tmp_file = mount_dir + '/tmpfile'
367
368    try:
369
370        # 128MiB volume
371        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
372
373        # Mount the image so we can populate it.
374        check_call('mkdir -p %s' % mount_dir, shell=True)
375        mount_fs(fs_type, fs_img, mount_dir)
376
377        # Create a test directory
378        check_call('mkdir %s/dir1' % mount_dir, shell=True)
379
380        # Create a small file and calculate md5
381        check_call('dd if=/dev/urandom of=%s bs=1K count=20'
382            % min_file, shell=True)
383        out = check_output(
384            'dd if=%s bs=1K 2> /dev/null | md5sum'
385            % min_file, shell=True)
386        md5val = [ out.split()[0] ]
387
388        # Calculate md5sum of Test Case 4
389        check_call('dd if=%s of=%s bs=1K count=20'
390            % (min_file, tmp_file), shell=True)
391        check_call('dd if=%s of=%s bs=1K seek=5 count=20'
392            % (min_file, tmp_file), shell=True)
393        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
394            % tmp_file, shell=True)
395        md5val.append(out.split()[0])
396
397        # Calculate md5sum of Test Case 5
398        check_call('dd if=%s of=%s bs=1K count=20'
399            % (min_file, tmp_file), shell=True)
400        check_call('dd if=%s of=%s bs=1K seek=5 count=5'
401            % (min_file, tmp_file), shell=True)
402        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
403            % tmp_file, shell=True)
404        md5val.append(out.split()[0])
405
406        # Calculate md5sum of Test Case 7
407        check_call('dd if=%s of=%s bs=1K count=20'
408            % (min_file, tmp_file), shell=True)
409        check_call('dd if=%s of=%s bs=1K seek=20 count=20'
410            % (min_file, tmp_file), shell=True)
411        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
412            % tmp_file, shell=True)
413        md5val.append(out.split()[0])
414
415        check_call('rm %s' % tmp_file, shell=True)
416        umount_fs(mount_dir)
417    except CalledProcessError:
418        pytest.skip('Setup failed for filesystem: ' + fs_type)
419        return
420    else:
421        yield [fs_ubtype, fs_img, md5val]
422    finally:
423        umount_fs(mount_dir)
424        call('rmdir %s' % mount_dir, shell=True)
425        if fs_img:
426            call('rm -f %s' % fs_img, shell=True)
427
428#
429# Fixture for mkdir test
430#
431# NOTE: yield_fixture was deprecated since pytest-3.0
432@pytest.yield_fixture()
433def fs_obj_mkdir(request, u_boot_config):
434    """Set up a file system to be used in mkdir test.
435
436    Args:
437        request: Pytest request object.
438	u_boot_config: U-boot configuration.
439
440    Return:
441        A fixture for mkdir test, i.e. a duplet of file system type and
442        volume file name.
443    """
444    fs_type = request.param
445    fs_img = ''
446
447    fs_ubtype = fstype_to_ubname(fs_type)
448    check_ubconfig(u_boot_config, fs_ubtype)
449
450    try:
451        # 128MiB volume
452        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
453    except:
454        pytest.skip('Setup failed for filesystem: ' + fs_type)
455    else:
456        yield [fs_ubtype, fs_img]
457    finally:
458        if fs_img:
459            call('rm -f %s' % fs_img, shell=True)
460
461#
462# Fixture for unlink test
463#
464# NOTE: yield_fixture was deprecated since pytest-3.0
465@pytest.yield_fixture()
466def fs_obj_unlink(request, u_boot_config):
467    """Set up a file system to be used in unlink test.
468
469    Args:
470        request: Pytest request object.
471	u_boot_config: U-boot configuration.
472
473    Return:
474        A fixture for unlink test, i.e. a duplet of file system type and
475        volume file name.
476    """
477    fs_type = request.param
478    fs_img = ''
479
480    fs_ubtype = fstype_to_ubname(fs_type)
481    check_ubconfig(u_boot_config, fs_ubtype)
482
483    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
484
485    try:
486
487        # 128MiB volume
488        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
489
490        # Mount the image so we can populate it.
491        check_call('mkdir -p %s' % mount_dir, shell=True)
492        mount_fs(fs_type, fs_img, mount_dir)
493
494        # Test Case 1 & 3
495        check_call('mkdir %s/dir1' % mount_dir, shell=True)
496        check_call('dd if=/dev/urandom of=%s/dir1/file1 bs=1K count=1'
497                                    % mount_dir, shell=True)
498        check_call('dd if=/dev/urandom of=%s/dir1/file2 bs=1K count=1'
499                                    % mount_dir, shell=True)
500
501        # Test Case 2
502        check_call('mkdir %s/dir2' % mount_dir, shell=True)
503	for i in range(0, 20):
504	    check_call('mkdir %s/dir2/0123456789abcdef%02x'
505                                    % (mount_dir, i), shell=True)
506
507        # Test Case 4
508        check_call('mkdir %s/dir4' % mount_dir, shell=True)
509
510        # Test Case 5, 6 & 7
511        check_call('mkdir %s/dir5' % mount_dir, shell=True)
512        check_call('dd if=/dev/urandom of=%s/dir5/file1 bs=1K count=1'
513                                    % mount_dir, shell=True)
514
515        umount_fs(mount_dir)
516    except CalledProcessError:
517        pytest.skip('Setup failed for filesystem: ' + fs_type)
518        return
519    else:
520        yield [fs_ubtype, fs_img]
521    finally:
522        umount_fs(mount_dir)
523        call('rmdir %s' % mount_dir, shell=True)
524        if fs_img:
525            call('rm -f %s' % fs_img, shell=True)
526