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