conftest.py 19 KB

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