Merge tag 'dm-pull-29oct19' of git://git.denx.de/u-boot-dm
[oweals/u-boot.git] / test / py / tests / test_fs / conftest.py
1 # SPDX-License-Identifier:      GPL-2.0+
2 # Copyright (c) 2018, Linaro Limited
3 # Author: Takahiro Akashi <takahiro.akashi@linaro.org>
4
5 import os
6 import os.path
7 import pytest
8 import re
9 from subprocess import call, check_call, check_output, CalledProcessError
10 from fstest_defs import *
11
12 supported_fs_basic = ['fat16', 'fat32', 'ext4']
13 supported_fs_ext = ['fat16', 'fat32']
14 supported_fs_mkdir = ['fat16', 'fat32']
15 supported_fs_unlink = ['fat16', 'fat32']
16 supported_fs_symlink = ['ext4']
17
18 #
19 # Filesystem test specific setup
20 #
21 def 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
35 def 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
66 def 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 #
97 def 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
115 def 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
134 def 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
176 def 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
191 fuse_mounted = False
192
193 def 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
225 def 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()
246 def 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()
354 def 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()
441 def 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()
474 def 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()
540 def 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)