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