2 # SPDX-License-Identifier: GPL-2.0-only
4 # Copyright (c) 2019 Western Digital Corporation or its affiliates.
9 # Automate running of fio tests
12 # python3 run-fio-tests.py [-r fio-root] [-f fio-path] [-a artifact-root]
13 # [--skip # # #...] [--run-only # # #...]
17 # # git clone git://git.kernel.dk/fio.git
20 # # python3 t/run-fio-tests.py
24 # - Python 3.5 (subprocess.run)
25 # - Linux (libaio ioengine, zbd tests, etc)
26 # - The artifact directory must be on a file system that accepts 512-byte IO
27 # (t0002, t0003, t0004).
28 # - The artifact directory needs to be on an SSD. Otherwise tests that carry
29 # out file-based IO will trigger a timeout (t0006).
31 # - SciPy (steadystate_tests.py)
32 # - libzbc (zbd tests)
33 # - root privileges (zbd test)
34 # - kernel 4.19 or later for zoned null block devices (zbd tests)
35 # - CUnit support (unittests)
40 # TODO run multiple tests simultaneously
41 # TODO Add sgunmap tests (requires SAS SSD)
51 from pathlib import Path
52 from statsmodels.sandbox.stats.runs import runstest_1samp
53 from fiotestlib import FioExeTest, FioJobFileTest, run_fio_tests
54 from fiotestcommon import *
57 class FioJobFileTest_t0005(FioJobFileTest):
58 """Test consists of fio test job t0005
59 Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400"""
61 def check_result(self):
62 super().check_result()
67 if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400:
68 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
70 if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400:
71 self.failure_reason = f"{self.failure_reason} bytes written mismatch,"
75 class FioJobFileTest_t0006(FioJobFileTest):
76 """Test consists of fio test job t0006
77 Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']"""
79 def check_result(self):
80 super().check_result()
85 ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
86 / self.json_data['jobs'][0]['write']['io_kbytes']
87 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
88 if ratio < 1.99 or ratio > 2.01:
89 self.failure_reason = f"{self.failure_reason} read/write ratio mismatch,"
93 class FioJobFileTest_t0007(FioJobFileTest):
94 """Test consists of fio test job t0007
95 Confirm that read['io_kbytes'] = 87040"""
97 def check_result(self):
98 super().check_result()
103 if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040:
104 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
108 class FioJobFileTest_t0008(FioJobFileTest):
109 """Test consists of fio test job t0008
110 Confirm that read['io_kbytes'] = 32768 and that
111 write['io_kbytes'] ~ 16384
113 This is a 50/50 seq read/write workload. Since fio flips a coin to
114 determine whether to issue a read or a write, total bytes written will not
115 be exactly 16384K. But total bytes read will be exactly 32768K because
116 reads will include the initial phase as well as the verify phase where all
117 the blocks originally written will be read."""
119 def check_result(self):
120 super().check_result()
125 ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16384
126 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
128 if ratio < 0.97 or ratio > 1.03:
129 self.failure_reason = f"{self.failure_reason} bytes written mismatch,"
131 if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768:
132 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
136 class FioJobFileTest_t0009(FioJobFileTest):
137 """Test consists of fio test job t0009
138 Confirm that runtime >= 60s"""
140 def check_result(self):
141 super().check_result()
146 logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
148 if self.json_data['jobs'][0]['elapsed'] < 60:
149 self.failure_reason = f"{self.failure_reason} elapsed time mismatch,"
153 class FioJobFileTest_t0012(FioJobFileTest):
154 """Test consists of fio test job t0012
155 Confirm ratios of job iops are 1:5:10
156 job1,job2,job3 respectively"""
158 def check_result(self):
159 super().check_result()
165 for i in range(1, 4):
166 filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename(
168 file_data = self.get_file_fail(filename)
172 iops_files.append(file_data.splitlines())
174 # there are 9 samples for job1 and job2, 4 samples for job3
179 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
180 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
181 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
185 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
186 "job3/job2={4:.3f} job3/job1={5:.3f}".format(i, iops1, iops2, iops3, ratio1,
189 # test job1 and job2 succeeded to recalibrate
190 if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13:
191 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
192 "expected r1~2 r2~10 got r1={3:.3f} r2={4:.3f},".format(iops1, iops2, iops3,
198 class FioJobFileTest_t0014(FioJobFileTest):
199 """Test consists of fio test job t0014
200 Confirm that job1_iops / job2_iops ~ 1:2 for entire duration
201 and that job1_iops / job3_iops ~ 1:3 for first half of duration.
203 The test is about making sure the flow feature can
204 re-calibrate the activity dynamically"""
206 def check_result(self):
207 super().check_result()
213 for i in range(1, 4):
214 filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename(
216 file_data = self.get_file_fail(filename)
220 iops_files.append(file_data.splitlines())
222 # there are 9 samples for job1 and job2, 4 samples for job3
228 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
230 ratio1 = iops1 / iops2
231 ratio2 = iops1 / iops3
234 if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45:
235 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
236 "expected r1~0.5 r2~0.33 got r1={3:.3f} r2={4:.3f},".format(
237 iops1, iops2, iops3, ratio1, ratio2)
240 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
241 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
245 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
246 "job1/job2={4:.3f} job1/job3={5:.3f}".format(i, iops1, iops2, iops3,
249 # test job1 and job2 succeeded to recalibrate
250 if ratio1 < 0.43 or ratio1 > 0.57:
251 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} expected ratio~0.5 " \
252 "got ratio={2:.3f},".format(iops1, iops2, ratio1)
257 class FioJobFileTest_t0015(FioJobFileTest):
258 """Test consists of fio test jobs t0015 and t0016
259 Confirm that mean(slat) + mean(clat) = mean(tlat)"""
261 def check_result(self):
262 super().check_result()
267 slat = self.json_data['jobs'][0]['read']['slat_ns']['mean']
268 clat = self.json_data['jobs'][0]['read']['clat_ns']['mean']
269 tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean']
270 logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat)
272 if abs(slat + clat - tlat) > 1:
273 self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format(
274 self.failure_reason, slat, clat, slat+clat, tlat)
278 class FioJobFileTest_t0019(FioJobFileTest):
279 """Test consists of fio test job t0019
280 Confirm that all offsets were touched sequentially"""
282 def check_result(self):
283 super().check_result()
285 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
286 file_data = self.get_file_fail(bw_log_filename)
290 log_lines = file_data.split('\n')
293 for line in log_lines:
294 if len(line.strip()) == 0:
296 cur = int(line.split(',')[4])
297 if cur - prev != 4096:
299 self.failure_reason = f"offsets {prev}, {cur} not sequential"
305 self.failure_reason = f"unexpected last offset {cur}"
308 class FioJobFileTest_t0020(FioJobFileTest):
309 """Test consists of fio test jobs t0020 and t0021
310 Confirm that almost all offsets were touched non-sequentially"""
312 def check_result(self):
313 super().check_result()
315 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
316 file_data = self.get_file_fail(bw_log_filename)
320 log_lines = file_data.split('\n')
324 prev = int(log_lines[0].split(',')[4])
325 for line in log_lines[1:]:
326 offsets.append(prev/4096)
327 if len(line.strip()) == 0:
329 cur = int(line.split(',')[4])
332 if len(offsets) != 256:
334 self.failure_reason += f" number of offsets is {len(offsets)} instead of 256"
339 self.failure_reason += f" missing offset {i * 4096}"
341 (_, p) = runstest_1samp(list(offsets))
344 self.failure_reason += f" runs test failed with p = {p}"
347 class FioJobFileTest_t0022(FioJobFileTest):
348 """Test consists of fio test job t0022"""
350 def check_result(self):
351 super().check_result()
353 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
354 file_data = self.get_file_fail(bw_log_filename)
358 log_lines = file_data.split('\n')
365 prev = int(log_lines[0].split(',')[4])
366 for line in log_lines[1:]:
368 if len(line.strip()) == 0:
370 cur = int(line.split(',')[4])
375 # 10 is an arbitrary threshold
378 self.failure_reason = f"too many ({seq_count}) consecutive offsets"
380 if len(offsets) == filesize/bs:
382 self.failure_reason += " no duplicate offsets found with norandommap=1"
385 class FioJobFileTest_t0023(FioJobFileTest):
386 """Test consists of fio test job t0023 randtrimwrite test."""
388 def check_trimwrite(self, filename):
389 """Make sure that trims are followed by writes of the same size at the same offset."""
391 bw_log_filename = os.path.join(self.paths['test_dir'], filename)
392 file_data = self.get_file_fail(bw_log_filename)
396 log_lines = file_data.split('\n')
399 for line in log_lines:
400 if len(line.strip()) == 0:
402 vals = line.split(',')
405 offset = int(vals[4])
409 self.failure_reason += " {0}: write not preceeded by trim: {1}".format(
410 bw_log_filename, line)
413 if ddir != 1: # pylint: disable=no-else-break
415 self.failure_reason += " {0}: trim not preceeded by write: {1}".format(
416 bw_log_filename, line)
421 self.failure_reason += " {0}: block size does not match: {1}".format(
422 bw_log_filename, line)
425 if prev_offset != offset:
427 self.failure_reason += " {0}: offset does not match: {1}".format(
428 bw_log_filename, line)
436 def check_all_offsets(self, filename, sectorsize, filesize):
437 """Make sure all offsets were touched."""
439 file_data = self.get_file_fail(os.path.join(self.paths['test_dir'], filename))
443 log_lines = file_data.split('\n')
447 for line in log_lines:
448 if len(line.strip()) == 0:
450 vals = line.split(',')
452 offset = int(vals[4])
453 if offset % sectorsize != 0:
455 self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(
456 filename, offset, sectorsize)
458 if bs % sectorsize != 0:
460 self.failure_reason += " {0}: block size {1} not a multiple of sector size " \
461 "{2}".format(filename, bs, sectorsize)
463 for i in range(int(bs/sectorsize)):
464 offsets.add(offset/sectorsize + i)
466 if len(offsets) != filesize/sectorsize:
468 self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(
469 filename, len(offsets), filesize/sectorsize)
471 logging.debug("%s: %d sectors touched", filename, len(offsets))
474 def check_result(self):
475 super().check_result()
479 self.check_trimwrite("basic_bw.log")
480 self.check_trimwrite("bs_bw.log")
481 self.check_trimwrite("bsrange_bw.log")
482 self.check_trimwrite("bssplit_bw.log")
483 self.check_trimwrite("basic_no_rm_bw.log")
484 self.check_trimwrite("bs_no_rm_bw.log")
485 self.check_trimwrite("bsrange_no_rm_bw.log")
486 self.check_trimwrite("bssplit_no_rm_bw.log")
488 self.check_all_offsets("basic_bw.log", 4096, filesize)
489 self.check_all_offsets("bs_bw.log", 8192, filesize)
490 self.check_all_offsets("bsrange_bw.log", 512, filesize)
491 self.check_all_offsets("bssplit_bw.log", 512, filesize)
494 class FioJobFileTest_t0024(FioJobFileTest_t0023):
495 """Test consists of fio test job t0024 trimwrite test."""
497 def check_result(self):
498 # call FioJobFileTest_t0023's parent to skip checks done by t0023
499 super(FioJobFileTest_t0023, self).check_result()
503 self.check_trimwrite("basic_bw.log")
504 self.check_trimwrite("bs_bw.log")
505 self.check_trimwrite("bsrange_bw.log")
506 self.check_trimwrite("bssplit_bw.log")
508 self.check_all_offsets("basic_bw.log", 4096, filesize)
509 self.check_all_offsets("bs_bw.log", 8192, filesize)
510 self.check_all_offsets("bsrange_bw.log", 512, filesize)
511 self.check_all_offsets("bssplit_bw.log", 512, filesize)
514 class FioJobFileTest_t0025(FioJobFileTest):
515 """Test experimental verify read backs written data pattern."""
516 def check_result(self):
517 super().check_result()
522 if self.json_data['jobs'][0]['read']['io_kbytes'] != 128:
525 class FioJobFileTest_t0027(FioJobFileTest):
526 def setup(self, *args, **kws):
527 super().setup(*args, **kws)
528 self.pattern_file = os.path.join(self.paths['test_dir'], "t0027.pattern")
529 self.output_file = os.path.join(self.paths['test_dir'], "t0027file")
530 self.pattern = os.urandom(16 << 10)
531 with open(self.pattern_file, "wb") as f:
532 f.write(self.pattern)
534 def check_result(self):
535 super().check_result()
540 with open(self.output_file, "rb") as f:
543 if data != self.pattern:
546 class FioJobFileTest_t0029(FioJobFileTest):
547 """Test loops option works with read-verify workload."""
548 def check_result(self):
549 super().check_result()
554 if self.json_data['jobs'][1]['read']['io_kbytes'] != 8:
557 class FioJobFileTest_LogFileFormat(FioJobFileTest):
558 """Test log file format"""
559 def setup(self, *args, **kws):
560 super().setup(*args, **kws)
563 def check_result(self):
564 super().check_result()
569 for logfile in self.patterns.keys():
570 file_path = os.path.join(self.paths['test_dir'], logfile)
571 with open(file_path, "r") as f:
573 if not re.match(self.patterns[logfile], line):
575 self.failure_reason = "wrong log file format: " + logfile
578 class FioJobFileTest_t0033(FioJobFileTest_LogFileFormat):
579 """Test log file format"""
580 def setup(self, *args, **kws):
581 super().setup(*args, **kws)
583 'log_bw.1.log': '\\d+, \\d+, \\d+, \\d+, 0x[\\da-f]+\\n',
584 'log_clat.2.log': '\\d+, \\d+, \\d+, \\d+, 0, \\d+\\n',
585 'log_iops.3.log': '\\d+, \\d+, \\d+, \\d+, \\d+, 0x[\\da-f]+\\n',
586 'log_iops.4.log': '\\d+, \\d+, \\d+, \\d+, 0, 0, \\d+\\n',
589 class FioJobFileTest_t0034(FioJobFileTest_LogFileFormat):
590 """Test log file format"""
591 def setup(self, *args, **kws):
592 super().setup(*args, **kws)
594 'log_clat.1.log': '\\d+, \\d+, \\d+, \\d+, \\d+, \\d+, \\d+\\n',
595 'log_slat.1.log': '\\d+, \\d+, \\d+, \\d+, \\d+, \\d+, \\d+\\n',
596 'log_lat.1.log': '\\d+, \\d+, \\d+, \\d+, \\d+, \\d+, 0\\n',
597 'log_clat.2.log': '\\d+, \\d+, \\d+, \\d+, 0, 0, \\d+, 0\\n',
598 'log_bw.3.log': '\\d+, \\d+, \\d+, \\d+, \\d+, \\d+, 0\\n',
599 'log_iops.3.log': '\\d+, \\d+, \\d+, \\d+, \\d+, \\d+, 0\\n',
602 class FioJobFileTest_iops_rate(FioJobFileTest):
603 """Test consists of fio test job t0011
604 Confirm that job0 iops == 1000
605 and that job1_iops / job0_iops ~ 8
606 With two runs of fio-3.16 I observed a ratio of 8.3"""
608 def check_result(self):
609 super().check_result()
614 iops1 = self.json_data['jobs'][0]['read']['iops']
615 logging.debug("Test %d: iops1: %f", self.testnum, iops1)
616 iops2 = self.json_data['jobs'][1]['read']['iops']
617 logging.debug("Test %d: iops2: %f", self.testnum, iops2)
618 ratio = iops2 / iops1
619 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
621 if iops1 < 950 or iops1 > 1050:
622 self.failure_reason = f"{self.failure_reason} iops value mismatch,"
625 if ratio < 6 or ratio > 10:
626 self.failure_reason = f"{self.failure_reason} iops ratio mismatch,"
633 'test_class': FioJobFileTest,
634 'job': 't0001-52c58027.fio',
635 'success': SUCCESS_DEFAULT,
642 'test_class': FioJobFileTest,
643 'job': 't0002-13af05ae-post.fio',
644 'success': SUCCESS_DEFAULT,
645 'pre_job': 't0002-13af05ae-pre.fio',
647 'requirements': [Requirements.linux, Requirements.libaio],
651 'test_class': FioJobFileTest,
652 'job': 't0003-0ae2c6e1-post.fio',
653 'success': SUCCESS_NONZERO,
654 'pre_job': 't0003-0ae2c6e1-pre.fio',
655 'pre_success': SUCCESS_DEFAULT,
656 'requirements': [Requirements.linux, Requirements.libaio],
660 'test_class': FioJobFileTest,
661 'job': 't0004-8a99fdf6.fio',
662 'success': SUCCESS_DEFAULT,
665 'requirements': [Requirements.linux, Requirements.libaio],
669 'test_class': FioJobFileTest_t0005,
670 'job': 't0005-f7078f7b.fio',
671 'success': SUCCESS_DEFAULT,
674 'output_format': 'json',
675 'requirements': [Requirements.not_windows],
679 'test_class': FioJobFileTest_t0006,
680 'job': 't0006-82af2a7c.fio',
681 'success': SUCCESS_DEFAULT,
684 'output_format': 'json',
685 'requirements': [Requirements.linux, Requirements.libaio],
689 'test_class': FioJobFileTest_t0007,
690 'job': 't0007-37cf9e3c.fio',
691 'success': SUCCESS_DEFAULT,
694 'output_format': 'json',
699 'test_class': FioJobFileTest_t0008,
700 'job': 't0008-ae2fafc8.fio',
701 'success': SUCCESS_DEFAULT,
704 'output_format': 'json',
709 'test_class': FioJobFileTest_t0009,
710 'job': 't0009-f8b0bd10.fio',
711 'success': SUCCESS_DEFAULT,
714 'output_format': 'json',
715 'requirements': [Requirements.not_macos,
716 Requirements.cpucount4],
717 # mac os does not support CPU affinity
721 'test_class': FioJobFileTest,
722 'job': 't0010-b7aae4ba.fio',
723 'success': SUCCESS_DEFAULT,
730 'test_class': FioJobFileTest_iops_rate,
731 'job': 't0011-5d2788d5.fio',
732 'success': SUCCESS_DEFAULT,
735 'output_format': 'json',
740 'test_class': FioJobFileTest_t0012,
742 'success': SUCCESS_DEFAULT,
745 'output_format': 'json',
750 'test_class': FioJobFileTest,
752 'success': SUCCESS_DEFAULT,
755 'output_format': 'json',
760 'test_class': FioJobFileTest_t0014,
762 'success': SUCCESS_DEFAULT,
765 'output_format': 'json',
770 'test_class': FioJobFileTest_t0015,
771 'job': 't0015-4e7e7898.fio',
772 'success': SUCCESS_DEFAULT,
775 'output_format': 'json',
776 'requirements': [Requirements.linux, Requirements.libaio],
780 'test_class': FioJobFileTest_t0015,
781 'job': 't0016-d54ae22.fio',
782 'success': SUCCESS_DEFAULT,
785 'output_format': 'json',
790 'test_class': FioJobFileTest_t0015,
792 'success': SUCCESS_DEFAULT,
795 'output_format': 'json',
796 'requirements': [Requirements.not_windows],
800 'test_class': FioJobFileTest,
802 'success': SUCCESS_DEFAULT,
805 'requirements': [Requirements.linux, Requirements.io_uring],
809 'test_class': FioJobFileTest_t0019,
811 'success': SUCCESS_DEFAULT,
818 'test_class': FioJobFileTest_t0020,
820 'success': SUCCESS_DEFAULT,
827 'test_class': FioJobFileTest_t0020,
829 'success': SUCCESS_DEFAULT,
836 'test_class': FioJobFileTest_t0022,
838 'success': SUCCESS_DEFAULT,
845 'test_class': FioJobFileTest_t0023,
847 'success': SUCCESS_DEFAULT,
854 'test_class': FioJobFileTest_t0024,
856 'success': SUCCESS_DEFAULT,
863 'test_class': FioJobFileTest_t0025,
865 'success': SUCCESS_DEFAULT,
868 'output_format': 'json',
873 'test_class': FioJobFileTest,
875 'success': SUCCESS_DEFAULT,
878 'requirements': [Requirements.not_windows],
882 'test_class': FioJobFileTest_t0027,
884 'success': SUCCESS_DEFAULT,
891 'test_class': FioJobFileTest,
892 'job': 't0028-c6cade16.fio',
893 'success': SUCCESS_DEFAULT,
900 'test_class': FioJobFileTest_t0029,
902 'success': SUCCESS_DEFAULT,
905 'output_format': 'json',
910 'test_class': FioJobFileTest,
912 'success': SUCCESS_DEFAULT,
915 'parameters': ['--bandwidth-log'],
920 'test_class': FioJobFileTest,
922 'success': SUCCESS_DEFAULT,
923 'pre_job': 't0031-pre.fio',
924 'pre_success': SUCCESS_DEFAULT,
925 'requirements': [Requirements.linux, Requirements.libaio],
929 'test_class': FioJobFileTest_t0033,
931 'success': SUCCESS_DEFAULT,
934 'requirements': [Requirements.linux, Requirements.libaio],
938 'test_class': FioJobFileTest_t0034,
940 'success': SUCCESS_DEFAULT,
943 'requirements': [Requirements.linux, Requirements.libaio],
947 'test_class': FioJobFileTest,
949 'success': SUCCESS_DEFAULT,
956 'test_class': FioJobFileTest,
957 'job': 't0036-post.fio',
958 'success': SUCCESS_DEFAULT,
959 'pre_job': 't0036-pre.fio',
960 'pre_success': SUCCESS_DEFAULT,
965 'test_class': FioJobFileTest,
966 'job': 't0037-post.fio',
967 'success': SUCCESS_DEFAULT,
968 'pre_job': 't0037-pre.fio',
969 'pre_success': SUCCESS_DEFAULT,
970 'requirements': [Requirements.linux, Requirements.libaio],
974 'test_class': FioExeTest,
977 'success': SUCCESS_DEFAULT,
982 'test_class': FioExeTest,
985 'success': SUCCESS_DEFAULT,
990 'test_class': FioExeTest,
991 'exe': 't/lfsr-test',
992 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
993 'success': SUCCESS_STDERR,
998 'test_class': FioExeTest,
999 'exe': 't/readonly.py',
1000 'parameters': ['-f', '{fio_path}'],
1001 'success': SUCCESS_DEFAULT,
1006 'test_class': FioExeTest,
1007 'exe': 't/steadystate_tests.py',
1008 'parameters': ['{fio_path}'],
1009 'success': SUCCESS_DEFAULT,
1014 'test_class': FioExeTest,
1017 'success': SUCCESS_STDERR,
1022 'test_class': FioExeTest,
1023 'exe': 't/strided.py',
1024 'parameters': ['--fio', '{fio_path}'],
1025 'success': SUCCESS_DEFAULT,
1030 'test_class': FioExeTest,
1031 'exe': 't/zbd/run-tests-against-nullb',
1032 'parameters': ['-s', '1'],
1033 'success': SUCCESS_DEFAULT,
1034 'requirements': [Requirements.linux, Requirements.zbd,
1039 'test_class': FioExeTest,
1040 'exe': 't/zbd/run-tests-against-nullb',
1041 'parameters': ['-s', '2'],
1042 'success': SUCCESS_DEFAULT,
1043 'requirements': [Requirements.linux, Requirements.zbd,
1044 Requirements.root, Requirements.zoned_nullb],
1048 'test_class': FioExeTest,
1049 'exe': 'unittests/unittest',
1051 'success': SUCCESS_DEFAULT,
1052 'requirements': [Requirements.unittests],
1056 'test_class': FioExeTest,
1057 'exe': 't/latency_percentiles.py',
1058 'parameters': ['-f', '{fio_path}'],
1059 'success': SUCCESS_DEFAULT,
1064 'test_class': FioExeTest,
1065 'exe': 't/jsonplus2csv_test.py',
1066 'parameters': ['-f', '{fio_path}'],
1067 'success': SUCCESS_DEFAULT,
1072 'test_class': FioExeTest,
1073 'exe': 't/log_compression.py',
1074 'parameters': ['-f', '{fio_path}'],
1075 'success': SUCCESS_DEFAULT,
1080 'test_class': FioExeTest,
1081 'exe': 't/random_seed.py',
1082 'parameters': ['-f', '{fio_path}'],
1083 'success': SUCCESS_DEFAULT,
1088 'test_class': FioExeTest,
1089 'exe': 't/nvmept.py',
1090 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'],
1091 'success': SUCCESS_DEFAULT,
1092 'requirements': [Requirements.linux, Requirements.nvmecdev],
1096 'test_class': FioExeTest,
1097 'exe': 't/nvmept_trim.py',
1098 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'],
1099 'success': SUCCESS_DEFAULT,
1100 'requirements': [Requirements.linux, Requirements.nvmecdev],
1104 'test_class': FioExeTest,
1105 'exe': 't/client_server.py',
1106 'parameters': ['-f', '{fio_path}'],
1107 'success': SUCCESS_DEFAULT,
1108 'requirements': [Requirements.linux],
1112 'test_class': FioExeTest,
1113 'exe': 't/verify.py',
1114 'parameters': ['-f', '{fio_path}'],
1115 'success': SUCCESS_LONG,
1120 'test_class': FioExeTest,
1121 'exe': 't/verify-trim.py',
1122 'parameters': ['-f', '{fio_path}'],
1123 'success': SUCCESS_DEFAULT,
1124 'requirements': [Requirements.linux],
1130 """Parse command-line arguments."""
1132 parser = argparse.ArgumentParser()
1133 parser.add_argument('-r', '--fio-root',
1134 help='fio root path')
1135 parser.add_argument('-f', '--fio',
1136 help='path to fio executable (e.g., ./fio)')
1137 parser.add_argument('-a', '--artifact-root',
1138 help='artifact root directory')
1139 parser.add_argument('-s', '--skip', nargs='+', type=int,
1140 help='list of test(s) to skip')
1141 parser.add_argument('-o', '--run-only', nargs='+', type=int,
1142 help='list of test(s) to run, skipping all others')
1143 parser.add_argument('-d', '--debug', action='store_true',
1144 help='provide debug output')
1145 parser.add_argument('-k', '--skip-req', action='store_true',
1146 help='skip requirements checking')
1147 parser.add_argument('-p', '--pass-through', action='append',
1148 help='pass-through an argument to an executable test')
1149 parser.add_argument('--nvmecdev', action='store', default=None,
1150 help='NVMe character device for **DESTRUCTIVE** testing (e.g., /dev/ng0n1)')
1151 args = parser.parse_args()
1161 logging.basicConfig(level=logging.DEBUG)
1163 logging.basicConfig(level=logging.INFO)
1166 if args.pass_through:
1167 for arg in args.pass_through:
1169 print(f"Invalid --pass-through argument '{arg}'")
1170 print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
1172 split = arg.split(":", 1)
1173 pass_through[int(split[0])] = split[1]
1174 logging.debug("Pass-through arguments: %s", pass_through)
1177 fio_root = args.fio_root
1179 fio_root = str(Path(__file__).absolute().parent.parent)
1180 print(f"fio root is {fio_root}")
1185 if platform.system() == "Windows":
1189 fio_path = os.path.join(fio_root, fio_exe)
1190 print(f"fio path is {fio_path}")
1191 if not shutil.which(fio_path):
1192 print("Warning: fio executable not found")
1194 artifact_root = args.artifact_root if args.artifact_root else \
1195 f"fio-test-{time.strftime('%Y%m%d-%H%M%S')}"
1196 os.mkdir(artifact_root)
1197 print(f"Artifact directory is {artifact_root}")
1199 if not args.skip_req:
1200 Requirements(fio_root, args)
1203 'fio_path': fio_path,
1204 'fio_root': fio_root,
1205 'artifact_root': artifact_root,
1206 'pass_through': pass_through,
1208 _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
1212 if __name__ == '__main__':