Merge branch 'master' of https://github.com/celestinechen/fio
[fio.git] / t / run-fio-tests.py
CommitLineData
df1eaa36
VF
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0-only
3#
4# Copyright (c) 2019 Western Digital Corporation or its affiliates.
5#
6"""
7# run-fio-tests.py
8#
9# Automate running of fio tests
10#
11# USAGE
12# python3 run-fio-tests.py [-r fio-root] [-f fio-path] [-a artifact-root]
13# [--skip # # #...] [--run-only # # #...]
14#
15#
16# EXAMPLE
b048455f 17# # git clone git://git.kernel.dk/fio.git
df1eaa36
VF
18# # cd fio
19# # make -j
20# # python3 t/run-fio-tests.py
21#
22#
23# REQUIREMENTS
b1bc705e 24# - Python 3.5 (subprocess.run)
df1eaa36
VF
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).
30# - 4 CPUs (t0009)
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)
36#
37"""
38
39#
40# TODO run multiple tests simultaneously
41# TODO Add sgunmap tests (requires SAS SSD)
df1eaa36
VF
42#
43
44import os
45import sys
df1eaa36 46import time
6d5470c3 47import shutil
df1eaa36
VF
48import logging
49import argparse
df1eaa36 50from pathlib import Path
888dbd62 51from statsmodels.sandbox.stats.runs import runstest_1samp
0dc6e911 52from fiotestlib import FioExeTest, FioJobFileTest, run_fio_tests
fb551941 53from fiotestcommon import *
df1eaa36
VF
54
55
0dc6e911 56class FioJobFileTest_t0005(FioJobFileTest):
df1eaa36
VF
57 """Test consists of fio test job t0005
58 Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400"""
59
60 def check_result(self):
1b4ba547 61 super().check_result()
df1eaa36
VF
62
63 if not self.passed:
64 return
65
66 if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400:
1b4ba547 67 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
df1eaa36
VF
68 self.passed = False
69 if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400:
1b4ba547 70 self.failure_reason = f"{self.failure_reason} bytes written mismatch,"
df1eaa36
VF
71 self.passed = False
72
73
0dc6e911 74class FioJobFileTest_t0006(FioJobFileTest):
df1eaa36
VF
75 """Test consists of fio test job t0006
76 Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']"""
77
78 def check_result(self):
1b4ba547 79 super().check_result()
df1eaa36
VF
80
81 if not self.passed:
82 return
83
84 ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
85 / self.json_data['jobs'][0]['write']['io_kbytes']
704cc4df 86 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 87 if ratio < 1.99 or ratio > 2.01:
1b4ba547 88 self.failure_reason = f"{self.failure_reason} read/write ratio mismatch,"
df1eaa36
VF
89 self.passed = False
90
91
0dc6e911 92class FioJobFileTest_t0007(FioJobFileTest):
df1eaa36
VF
93 """Test consists of fio test job t0007
94 Confirm that read['io_kbytes'] = 87040"""
95
96 def check_result(self):
1b4ba547 97 super().check_result()
df1eaa36
VF
98
99 if not self.passed:
100 return
101
102 if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040:
1b4ba547 103 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
df1eaa36
VF
104 self.passed = False
105
106
0dc6e911 107class FioJobFileTest_t0008(FioJobFileTest):
df1eaa36
VF
108 """Test consists of fio test job t0008
109 Confirm that read['io_kbytes'] = 32768 and that
77c758db 110 write['io_kbytes'] ~ 16384
df1eaa36 111
77c758db
VF
112 This is a 50/50 seq read/write workload. Since fio flips a coin to
113 determine whether to issue a read or a write, total bytes written will not
114 be exactly 16384K. But total bytes read will be exactly 32768K because
115 reads will include the initial phase as well as the verify phase where all
116 the blocks originally written will be read."""
df1eaa36
VF
117
118 def check_result(self):
1b4ba547 119 super().check_result()
df1eaa36
VF
120
121 if not self.passed:
122 return
123
77c758db 124 ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16384
704cc4df 125 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 126
77c758db 127 if ratio < 0.97 or ratio > 1.03:
1b4ba547 128 self.failure_reason = f"{self.failure_reason} bytes written mismatch,"
df1eaa36
VF
129 self.passed = False
130 if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768:
1b4ba547 131 self.failure_reason = f"{self.failure_reason} bytes read mismatch,"
df1eaa36
VF
132 self.passed = False
133
134
0dc6e911 135class FioJobFileTest_t0009(FioJobFileTest):
df1eaa36
VF
136 """Test consists of fio test job t0009
137 Confirm that runtime >= 60s"""
138
139 def check_result(self):
1b4ba547 140 super().check_result()
df1eaa36
VF
141
142 if not self.passed:
143 return
144
704cc4df 145 logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
df1eaa36
VF
146
147 if self.json_data['jobs'][0]['elapsed'] < 60:
1b4ba547 148 self.failure_reason = f"{self.failure_reason} elapsed time mismatch,"
df1eaa36
VF
149 self.passed = False
150
151
0dc6e911 152class FioJobFileTest_t0012(FioJobFileTest):
d4e74fda
DB
153 """Test consists of fio test job t0012
154 Confirm ratios of job iops are 1:5:10
155 job1,job2,job3 respectively"""
156
157 def check_result(self):
1b4ba547 158 super().check_result()
d4e74fda
DB
159
160 if not self.passed:
161 return
162
163 iops_files = []
114eadb2 164 for i in range(1, 4):
68b3a741 165 filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename(
114eadb2 166 self.fio_job), i))
0f5234b4
VF
167 file_data = self.get_file_fail(filename)
168 if not file_data:
d4e74fda
DB
169 return
170
171 iops_files.append(file_data.splitlines())
172
173 # there are 9 samples for job1 and job2, 4 samples for job3
174 iops1 = 0.0
175 iops2 = 0.0
176 iops3 = 0.0
177 for i in range(9):
178 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
179 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
180 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
181
182 ratio1 = iops3/iops2
183 ratio2 = iops3/iops1
114eadb2
VF
184 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
185 "job3/job2={4:.3f} job3/job1={5:.3f}".format(i, iops1, iops2, iops3, ratio1,
186 ratio2))
d4e74fda
DB
187
188 # test job1 and job2 succeeded to recalibrate
189 if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13:
114eadb2
VF
190 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
191 "expected r1~2 r2~10 got r1={3:.3f} r2={4:.3f},".format(iops1, iops2, iops3,
192 ratio1, ratio2)
d4e74fda
DB
193 self.passed = False
194 return
195
196
0dc6e911 197class FioJobFileTest_t0014(FioJobFileTest):
d4e74fda
DB
198 """Test consists of fio test job t0014
199 Confirm that job1_iops / job2_iops ~ 1:2 for entire duration
200 and that job1_iops / job3_iops ~ 1:3 for first half of duration.
201
202 The test is about making sure the flow feature can
203 re-calibrate the activity dynamically"""
204
205 def check_result(self):
1b4ba547 206 super().check_result()
d4e74fda
DB
207
208 if not self.passed:
209 return
210
211 iops_files = []
114eadb2 212 for i in range(1, 4):
68b3a741 213 filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename(
114eadb2 214 self.fio_job), i))
0f5234b4
VF
215 file_data = self.get_file_fail(filename)
216 if not file_data:
d4e74fda
DB
217 return
218
219 iops_files.append(file_data.splitlines())
220
221 # there are 9 samples for job1 and job2, 4 samples for job3
222 iops1 = 0.0
223 iops2 = 0.0
224 iops3 = 0.0
225 for i in range(9):
226 if i < 4:
227 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
228 elif i == 4:
229 ratio1 = iops1 / iops2
230 ratio2 = iops1 / iops3
231
232
233 if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45:
114eadb2
VF
234 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
235 "expected r1~0.5 r2~0.33 got r1={3:.3f} r2={4:.3f},".format(
236 iops1, iops2, iops3, ratio1, ratio2)
d4e74fda
DB
237 self.passed = False
238
239 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
240 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
241
242 ratio1 = iops1/iops2
243 ratio2 = iops1/iops3
114eadb2
VF
244 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
245 "job1/job2={4:.3f} job1/job3={5:.3f}".format(i, iops1, iops2, iops3,
246 ratio1, ratio2))
d4e74fda
DB
247
248 # test job1 and job2 succeeded to recalibrate
249 if ratio1 < 0.43 or ratio1 > 0.57:
114eadb2
VF
250 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} expected ratio~0.5 " \
251 "got ratio={2:.3f},".format(iops1, iops2, ratio1)
d4e74fda
DB
252 self.passed = False
253 return
254
255
0dc6e911 256class FioJobFileTest_t0015(FioJobFileTest):
de31fe9a 257 """Test consists of fio test jobs t0015 and t0016
60ebb939
VF
258 Confirm that mean(slat) + mean(clat) = mean(tlat)"""
259
260 def check_result(self):
1b4ba547 261 super().check_result()
60ebb939
VF
262
263 if not self.passed:
264 return
265
266 slat = self.json_data['jobs'][0]['read']['slat_ns']['mean']
267 clat = self.json_data['jobs'][0]['read']['clat_ns']['mean']
268 tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean']
269 logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat)
270
271 if abs(slat + clat - tlat) > 1:
272 self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format(
273 self.failure_reason, slat, clat, slat+clat, tlat)
274 self.passed = False
275
276
0dc6e911 277class FioJobFileTest_t0019(FioJobFileTest):
ef54f290
VF
278 """Test consists of fio test job t0019
279 Confirm that all offsets were touched sequentially"""
280
281 def check_result(self):
1b4ba547 282 super().check_result()
ef54f290 283
68b3a741 284 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
0f5234b4
VF
285 file_data = self.get_file_fail(bw_log_filename)
286 if not file_data:
114eadb2
VF
287 return
288
ef54f290
VF
289 log_lines = file_data.split('\n')
290
291 prev = -4096
292 for line in log_lines:
293 if len(line.strip()) == 0:
294 continue
295 cur = int(line.split(',')[4])
296 if cur - prev != 4096:
297 self.passed = False
1b4ba547 298 self.failure_reason = f"offsets {prev}, {cur} not sequential"
ef54f290
VF
299 return
300 prev = cur
301
302 if cur/4096 != 255:
303 self.passed = False
1b4ba547 304 self.failure_reason = f"unexpected last offset {cur}"
ef54f290
VF
305
306
0dc6e911 307class FioJobFileTest_t0020(FioJobFileTest):
eb40b275 308 """Test consists of fio test jobs t0020 and t0021
ef54f290
VF
309 Confirm that almost all offsets were touched non-sequentially"""
310
311 def check_result(self):
1b4ba547 312 super().check_result()
ef54f290 313
68b3a741 314 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
0f5234b4
VF
315 file_data = self.get_file_fail(bw_log_filename)
316 if not file_data:
114eadb2
VF
317 return
318
ef54f290
VF
319 log_lines = file_data.split('\n')
320
888dbd62 321 offsets = []
ef54f290
VF
322
323 prev = int(log_lines[0].split(',')[4])
324 for line in log_lines[1:]:
888dbd62 325 offsets.append(prev/4096)
ef54f290
VF
326 if len(line.strip()) == 0:
327 continue
328 cur = int(line.split(',')[4])
ef54f290
VF
329 prev = cur
330
ef54f290
VF
331 if len(offsets) != 256:
332 self.passed = False
1b4ba547 333 self.failure_reason += f" number of offsets is {len(offsets)} instead of 256"
ef54f290
VF
334
335 for i in range(256):
336 if not i in offsets:
337 self.passed = False
1b4ba547 338 self.failure_reason += f" missing offset {i * 4096}"
ef54f290 339
1b4ba547 340 (_, p) = runstest_1samp(list(offsets))
888dbd62
VF
341 if p < 0.05:
342 self.passed = False
343 self.failure_reason += f" runs test failed with p = {p}"
344
ef54f290 345
0dc6e911 346class FioJobFileTest_t0022(FioJobFileTest):
eb40b275
VF
347 """Test consists of fio test job t0022"""
348
349 def check_result(self):
1b4ba547 350 super().check_result()
eb40b275 351
68b3a741 352 bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log")
0f5234b4
VF
353 file_data = self.get_file_fail(bw_log_filename)
354 if not file_data:
114eadb2
VF
355 return
356
eb40b275
VF
357 log_lines = file_data.split('\n')
358
359 filesize = 1024*1024
360 bs = 4096
361 seq_count = 0
362 offsets = set()
363
364 prev = int(log_lines[0].split(',')[4])
365 for line in log_lines[1:]:
366 offsets.add(prev/bs)
367 if len(line.strip()) == 0:
368 continue
369 cur = int(line.split(',')[4])
370 if cur - prev == bs:
371 seq_count += 1
372 prev = cur
373
374 # 10 is an arbitrary threshold
375 if seq_count > 10:
376 self.passed = False
1b4ba547 377 self.failure_reason = f"too many ({seq_count}) consecutive offsets"
eb40b275
VF
378
379 if len(offsets) == filesize/bs:
380 self.passed = False
114eadb2 381 self.failure_reason += " no duplicate offsets found with norandommap=1"
eb40b275
VF
382
383
0dc6e911 384class FioJobFileTest_t0023(FioJobFileTest):
21a20229 385 """Test consists of fio test job t0023 randtrimwrite test."""
c37183f8 386
1fb78294
VF
387 def check_trimwrite(self, filename):
388 """Make sure that trims are followed by writes of the same size at the same offset."""
389
68b3a741 390 bw_log_filename = os.path.join(self.paths['test_dir'], filename)
0f5234b4
VF
391 file_data = self.get_file_fail(bw_log_filename)
392 if not file_data:
114eadb2
VF
393 return
394
c37183f8
VF
395 log_lines = file_data.split('\n')
396
397 prev_ddir = 1
398 for line in log_lines:
399 if len(line.strip()) == 0:
400 continue
401 vals = line.split(',')
402 ddir = int(vals[2])
403 bs = int(vals[3])
404 offset = int(vals[4])
405 if prev_ddir == 1:
406 if ddir != 2:
407 self.passed = False
21a20229
VF
408 self.failure_reason += " {0}: write not preceeded by trim: {1}".format(
409 bw_log_filename, line)
c37183f8
VF
410 break
411 else:
1b4ba547 412 if ddir != 1: # pylint: disable=no-else-break
c37183f8 413 self.passed = False
21a20229
VF
414 self.failure_reason += " {0}: trim not preceeded by write: {1}".format(
415 bw_log_filename, line)
c37183f8
VF
416 break
417 else:
418 if prev_bs != bs:
419 self.passed = False
21a20229
VF
420 self.failure_reason += " {0}: block size does not match: {1}".format(
421 bw_log_filename, line)
c37183f8 422 break
1b4ba547 423
c37183f8
VF
424 if prev_offset != offset:
425 self.passed = False
21a20229
VF
426 self.failure_reason += " {0}: offset does not match: {1}".format(
427 bw_log_filename, line)
c37183f8 428 break
1b4ba547 429
c37183f8
VF
430 prev_ddir = ddir
431 prev_bs = bs
432 prev_offset = offset
433
434
9e83aa16 435 def check_all_offsets(self, filename, sectorsize, filesize):
21a20229
VF
436 """Make sure all offsets were touched."""
437
68b3a741 438 file_data = self.get_file_fail(os.path.join(self.paths['test_dir'], filename))
0f5234b4 439 if not file_data:
9e83aa16
VF
440 return
441
442 log_lines = file_data.split('\n')
443
444 offsets = set()
445
446 for line in log_lines:
447 if len(line.strip()) == 0:
448 continue
449 vals = line.split(',')
450 bs = int(vals[3])
451 offset = int(vals[4])
452 if offset % sectorsize != 0:
453 self.passed = False
21a20229
VF
454 self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(
455 filename, offset, sectorsize)
456 break
9e83aa16
VF
457 if bs % sectorsize != 0:
458 self.passed = False
21a20229
VF
459 self.failure_reason += " {0}: block size {1} not a multiple of sector size " \
460 "{2}".format(filename, bs, sectorsize)
461 break
9e83aa16
VF
462 for i in range(int(bs/sectorsize)):
463 offsets.add(offset/sectorsize + i)
464
465 if len(offsets) != filesize/sectorsize:
466 self.passed = False
21a20229
VF
467 self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(
468 filename, len(offsets), filesize/sectorsize)
9e83aa16 469 else:
21a20229 470 logging.debug("%s: %d sectors touched", filename, len(offsets))
9e83aa16
VF
471
472
c37183f8 473 def check_result(self):
1b4ba547 474 super().check_result()
c37183f8 475
9e83aa16
VF
476 filesize = 1024*1024
477
1fb78294
VF
478 self.check_trimwrite("basic_bw.log")
479 self.check_trimwrite("bs_bw.log")
480 self.check_trimwrite("bsrange_bw.log")
481 self.check_trimwrite("bssplit_bw.log")
482 self.check_trimwrite("basic_no_rm_bw.log")
483 self.check_trimwrite("bs_no_rm_bw.log")
484 self.check_trimwrite("bsrange_no_rm_bw.log")
485 self.check_trimwrite("bssplit_no_rm_bw.log")
c37183f8 486
9e83aa16
VF
487 self.check_all_offsets("basic_bw.log", 4096, filesize)
488 self.check_all_offsets("bs_bw.log", 8192, filesize)
489 self.check_all_offsets("bsrange_bw.log", 512, filesize)
490 self.check_all_offsets("bssplit_bw.log", 512, filesize)
c37183f8
VF
491
492
0dc6e911 493class FioJobFileTest_t0024(FioJobFileTest_t0023):
2d5c4600
VF
494 """Test consists of fio test job t0024 trimwrite test."""
495
496 def check_result(self):
0dc6e911
VF
497 # call FioJobFileTest_t0023's parent to skip checks done by t0023
498 super(FioJobFileTest_t0023, self).check_result()
2d5c4600
VF
499
500 filesize = 1024*1024
501
502 self.check_trimwrite("basic_bw.log")
503 self.check_trimwrite("bs_bw.log")
504 self.check_trimwrite("bsrange_bw.log")
505 self.check_trimwrite("bssplit_bw.log")
506
507 self.check_all_offsets("basic_bw.log", 4096, filesize)
508 self.check_all_offsets("bs_bw.log", 8192, filesize)
509 self.check_all_offsets("bsrange_bw.log", 512, filesize)
510 self.check_all_offsets("bssplit_bw.log", 512, filesize)
511
512
0dc6e911 513class FioJobFileTest_t0025(FioJobFileTest):
d661fb18
SK
514 """Test experimental verify read backs written data pattern."""
515 def check_result(self):
1b4ba547 516 super().check_result()
d661fb18
SK
517
518 if not self.passed:
519 return
520
521 if self.json_data['jobs'][0]['read']['io_kbytes'] != 128:
522 self.passed = False
523
0dc6e911 524class FioJobFileTest_t0027(FioJobFileTest):
ede04c27 525 def setup(self, *args, **kws):
1b4ba547 526 super().setup(*args, **kws)
68b3a741
VF
527 self.pattern_file = os.path.join(self.paths['test_dir'], "t0027.pattern")
528 self.output_file = os.path.join(self.paths['test_dir'], "t0027file")
ede04c27
LG
529 self.pattern = os.urandom(16 << 10)
530 with open(self.pattern_file, "wb") as f:
531 f.write(self.pattern)
532
533 def check_result(self):
1b4ba547 534 super().check_result()
ede04c27
LG
535
536 if not self.passed:
537 return
538
539 with open(self.output_file, "rb") as f:
540 data = f.read()
541
542 if data != self.pattern:
543 self.passed = False
d661fb18 544
7aec5ac0
SK
545class FioJobFileTest_t0029(FioJobFileTest):
546 """Test loops option works with read-verify workload."""
547 def check_result(self):
548 super().check_result()
549
550 if not self.passed:
551 return
552
553 if self.json_data['jobs'][1]['read']['io_kbytes'] != 8:
554 self.passed = False
555
0dc6e911 556class FioJobFileTest_iops_rate(FioJobFileTest):
12b609b5 557 """Test consists of fio test job t0011
df1eaa36
VF
558 Confirm that job0 iops == 1000
559 and that job1_iops / job0_iops ~ 8
560 With two runs of fio-3.16 I observed a ratio of 8.3"""
561
562 def check_result(self):
1b4ba547 563 super().check_result()
df1eaa36
VF
564
565 if not self.passed:
566 return
567
568 iops1 = self.json_data['jobs'][0]['read']['iops']
7ddc4ed1 569 logging.debug("Test %d: iops1: %f", self.testnum, iops1)
df1eaa36 570 iops2 = self.json_data['jobs'][1]['read']['iops']
7ddc4ed1 571 logging.debug("Test %d: iops2: %f", self.testnum, iops2)
df1eaa36 572 ratio = iops2 / iops1
704cc4df 573 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 574
0a9b8988 575 if iops1 < 950 or iops1 > 1050:
1b4ba547 576 self.failure_reason = f"{self.failure_reason} iops value mismatch,"
df1eaa36
VF
577 self.passed = False
578
d4e74fda 579 if ratio < 6 or ratio > 10:
1b4ba547 580 self.failure_reason = f"{self.failure_reason} iops ratio mismatch,"
df1eaa36
VF
581 self.passed = False
582
583
df1eaa36 584TEST_LIST = [
704cc4df
VF
585 {
586 'test_id': 1,
0dc6e911 587 'test_class': FioJobFileTest,
704cc4df
VF
588 'job': 't0001-52c58027.fio',
589 'success': SUCCESS_DEFAULT,
590 'pre_job': None,
591 'pre_success': None,
592 'requirements': [],
593 },
594 {
595 'test_id': 2,
0dc6e911 596 'test_class': FioJobFileTest,
704cc4df
VF
597 'job': 't0002-13af05ae-post.fio',
598 'success': SUCCESS_DEFAULT,
599 'pre_job': 't0002-13af05ae-pre.fio',
600 'pre_success': None,
601 'requirements': [Requirements.linux, Requirements.libaio],
602 },
603 {
604 'test_id': 3,
0dc6e911 605 'test_class': FioJobFileTest,
704cc4df
VF
606 'job': 't0003-0ae2c6e1-post.fio',
607 'success': SUCCESS_NONZERO,
608 'pre_job': 't0003-0ae2c6e1-pre.fio',
609 'pre_success': SUCCESS_DEFAULT,
610 'requirements': [Requirements.linux, Requirements.libaio],
611 },
612 {
613 'test_id': 4,
0dc6e911 614 'test_class': FioJobFileTest,
704cc4df
VF
615 'job': 't0004-8a99fdf6.fio',
616 'success': SUCCESS_DEFAULT,
617 'pre_job': None,
618 'pre_success': None,
619 'requirements': [Requirements.linux, Requirements.libaio],
620 },
621 {
622 'test_id': 5,
0dc6e911 623 'test_class': FioJobFileTest_t0005,
704cc4df
VF
624 'job': 't0005-f7078f7b.fio',
625 'success': SUCCESS_DEFAULT,
626 'pre_job': None,
627 'pre_success': None,
628 'output_format': 'json',
629 'requirements': [Requirements.not_windows],
630 },
631 {
632 'test_id': 6,
0dc6e911 633 'test_class': FioJobFileTest_t0006,
704cc4df
VF
634 'job': 't0006-82af2a7c.fio',
635 'success': SUCCESS_DEFAULT,
636 'pre_job': None,
637 'pre_success': None,
638 'output_format': 'json',
639 'requirements': [Requirements.linux, Requirements.libaio],
640 },
641 {
642 'test_id': 7,
0dc6e911 643 'test_class': FioJobFileTest_t0007,
704cc4df
VF
644 'job': 't0007-37cf9e3c.fio',
645 'success': SUCCESS_DEFAULT,
646 'pre_job': None,
647 'pre_success': None,
648 'output_format': 'json',
649 'requirements': [],
650 },
651 {
652 'test_id': 8,
0dc6e911 653 'test_class': FioJobFileTest_t0008,
704cc4df
VF
654 'job': 't0008-ae2fafc8.fio',
655 'success': SUCCESS_DEFAULT,
656 'pre_job': None,
657 'pre_success': None,
658 'output_format': 'json',
659 'requirements': [],
660 },
661 {
662 'test_id': 9,
0dc6e911 663 'test_class': FioJobFileTest_t0009,
704cc4df
VF
664 'job': 't0009-f8b0bd10.fio',
665 'success': SUCCESS_DEFAULT,
666 'pre_job': None,
667 'pre_success': None,
668 'output_format': 'json',
669 'requirements': [Requirements.not_macos,
670 Requirements.cpucount4],
671 # mac os does not support CPU affinity
672 },
673 {
674 'test_id': 10,
0dc6e911 675 'test_class': FioJobFileTest,
704cc4df
VF
676 'job': 't0010-b7aae4ba.fio',
677 'success': SUCCESS_DEFAULT,
678 'pre_job': None,
679 'pre_success': None,
680 'requirements': [],
681 },
682 {
683 'test_id': 11,
0dc6e911 684 'test_class': FioJobFileTest_iops_rate,
704cc4df
VF
685 'job': 't0011-5d2788d5.fio',
686 'success': SUCCESS_DEFAULT,
687 'pre_job': None,
688 'pre_success': None,
689 'output_format': 'json',
690 'requirements': [],
691 },
0a602473
BVA
692 {
693 'test_id': 12,
0dc6e911 694 'test_class': FioJobFileTest_t0012,
0a602473
BVA
695 'job': 't0012.fio',
696 'success': SUCCESS_DEFAULT,
697 'pre_job': None,
698 'pre_success': None,
699 'output_format': 'json',
d4e74fda 700 'requirements': [],
0a602473 701 },
f0c7ae7a
BVA
702 {
703 'test_id': 13,
0dc6e911 704 'test_class': FioJobFileTest,
f0c7ae7a
BVA
705 'job': 't0013.fio',
706 'success': SUCCESS_DEFAULT,
707 'pre_job': None,
708 'pre_success': None,
709 'output_format': 'json',
710 'requirements': [],
711 },
d4e74fda
DB
712 {
713 'test_id': 14,
0dc6e911 714 'test_class': FioJobFileTest_t0014,
d4e74fda
DB
715 'job': 't0014.fio',
716 'success': SUCCESS_DEFAULT,
717 'pre_job': None,
718 'pre_success': None,
719 'output_format': 'json',
720 'requirements': [],
721 },
60ebb939
VF
722 {
723 'test_id': 15,
0dc6e911 724 'test_class': FioJobFileTest_t0015,
5c763be5 725 'job': 't0015-4e7e7898.fio',
60ebb939
VF
726 'success': SUCCESS_DEFAULT,
727 'pre_job': None,
728 'pre_success': None,
729 'output_format': 'json',
730 'requirements': [Requirements.linux, Requirements.libaio],
731 },
de31fe9a
VF
732 {
733 'test_id': 16,
0dc6e911 734 'test_class': FioJobFileTest_t0015,
f045d5f8 735 'job': 't0016-d54ae22.fio',
de31fe9a
VF
736 'success': SUCCESS_DEFAULT,
737 'pre_job': None,
738 'pre_success': None,
739 'output_format': 'json',
740 'requirements': [],
741 },
31a58cba
VF
742 {
743 'test_id': 17,
0dc6e911 744 'test_class': FioJobFileTest_t0015,
31a58cba
VF
745 'job': 't0017.fio',
746 'success': SUCCESS_DEFAULT,
747 'pre_job': None,
748 'pre_success': None,
749 'output_format': 'json',
750 'requirements': [Requirements.not_windows],
751 },
a2947c33
VF
752 {
753 'test_id': 18,
0dc6e911 754 'test_class': FioJobFileTest,
a2947c33
VF
755 'job': 't0018.fio',
756 'success': SUCCESS_DEFAULT,
757 'pre_job': None,
758 'pre_success': None,
759 'requirements': [Requirements.linux, Requirements.io_uring],
760 },
ef54f290
VF
761 {
762 'test_id': 19,
0dc6e911 763 'test_class': FioJobFileTest_t0019,
ef54f290
VF
764 'job': 't0019.fio',
765 'success': SUCCESS_DEFAULT,
766 'pre_job': None,
767 'pre_success': None,
768 'requirements': [],
769 },
770 {
771 'test_id': 20,
0dc6e911 772 'test_class': FioJobFileTest_t0020,
ef54f290
VF
773 'job': 't0020.fio',
774 'success': SUCCESS_DEFAULT,
775 'pre_job': None,
776 'pre_success': None,
777 'requirements': [],
778 },
eb40b275
VF
779 {
780 'test_id': 21,
0dc6e911 781 'test_class': FioJobFileTest_t0020,
eb40b275
VF
782 'job': 't0021.fio',
783 'success': SUCCESS_DEFAULT,
784 'pre_job': None,
785 'pre_success': None,
786 'requirements': [],
787 },
788 {
789 'test_id': 22,
0dc6e911 790 'test_class': FioJobFileTest_t0022,
eb40b275
VF
791 'job': 't0022.fio',
792 'success': SUCCESS_DEFAULT,
793 'pre_job': None,
794 'pre_success': None,
795 'requirements': [],
796 },
c37183f8
VF
797 {
798 'test_id': 23,
0dc6e911 799 'test_class': FioJobFileTest_t0023,
c37183f8
VF
800 'job': 't0023.fio',
801 'success': SUCCESS_DEFAULT,
802 'pre_job': None,
803 'pre_success': None,
804 'requirements': [],
805 },
2d5c4600
VF
806 {
807 'test_id': 24,
0dc6e911 808 'test_class': FioJobFileTest_t0024,
2d5c4600
VF
809 'job': 't0024.fio',
810 'success': SUCCESS_DEFAULT,
811 'pre_job': None,
812 'pre_success': None,
813 'requirements': [],
814 },
d661fb18
SK
815 {
816 'test_id': 25,
0dc6e911 817 'test_class': FioJobFileTest_t0025,
d661fb18
SK
818 'job': 't0025.fio',
819 'success': SUCCESS_DEFAULT,
820 'pre_job': None,
821 'pre_success': None,
822 'output_format': 'json',
823 'requirements': [],
824 },
c4704c08
SK
825 {
826 'test_id': 26,
0dc6e911 827 'test_class': FioJobFileTest,
c4704c08
SK
828 'job': 't0026.fio',
829 'success': SUCCESS_DEFAULT,
830 'pre_job': None,
831 'pre_success': None,
832 'requirements': [Requirements.not_windows],
833 },
ede04c27
LG
834 {
835 'test_id': 27,
0dc6e911 836 'test_class': FioJobFileTest_t0027,
ede04c27
LG
837 'job': 't0027.fio',
838 'success': SUCCESS_DEFAULT,
839 'pre_job': None,
840 'pre_success': None,
841 'requirements': [],
842 },
23fb1642
VF
843 {
844 'test_id': 28,
0dc6e911 845 'test_class': FioJobFileTest,
23fb1642
VF
846 'job': 't0028-c6cade16.fio',
847 'success': SUCCESS_DEFAULT,
848 'pre_job': None,
849 'pre_success': None,
850 'requirements': [],
7aec5ac0
SK
851 },
852 {
853 'test_id': 29,
854 'test_class': FioJobFileTest_t0029,
855 'job': 't0029.fio',
856 'success': SUCCESS_DEFAULT,
857 'pre_job': None,
858 'pre_success': None,
859 'output_format': 'json',
860 'requirements': [],
23fb1642 861 },
f2262200
VF
862 {
863 'test_id': 30,
864 'test_class': FioJobFileTest,
865 'job': 't0030.fio',
866 'success': SUCCESS_DEFAULT,
867 'pre_job': None,
868 'pre_success': None,
869 'parameters': ['--bandwidth-log'],
870 'requirements': [],
871 },
140c58be
SK
872 {
873 'test_id': 31,
874 'test_class': FioJobFileTest,
875 'job': 't0031.fio',
876 'success': SUCCESS_DEFAULT,
877 'pre_job': 't0031-pre.fio',
878 'pre_success': SUCCESS_DEFAULT,
20f42c10 879 'requirements': [Requirements.linux, Requirements.libaio],
140c58be 880 },
704cc4df
VF
881 {
882 'test_id': 1000,
883 'test_class': FioExeTest,
884 'exe': 't/axmap',
885 'parameters': None,
886 'success': SUCCESS_DEFAULT,
887 'requirements': [],
888 },
889 {
890 'test_id': 1001,
891 'test_class': FioExeTest,
892 'exe': 't/ieee754',
893 'parameters': None,
894 'success': SUCCESS_DEFAULT,
895 'requirements': [],
896 },
897 {
898 'test_id': 1002,
899 'test_class': FioExeTest,
900 'exe': 't/lfsr-test',
901 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
902 'success': SUCCESS_STDERR,
903 'requirements': [],
904 },
905 {
906 'test_id': 1003,
907 'test_class': FioExeTest,
908 'exe': 't/readonly.py',
909 'parameters': ['-f', '{fio_path}'],
910 'success': SUCCESS_DEFAULT,
911 'requirements': [],
912 },
913 {
914 'test_id': 1004,
915 'test_class': FioExeTest,
916 'exe': 't/steadystate_tests.py',
917 'parameters': ['{fio_path}'],
918 'success': SUCCESS_DEFAULT,
919 'requirements': [],
920 },
921 {
922 'test_id': 1005,
923 'test_class': FioExeTest,
924 'exe': 't/stest',
925 'parameters': None,
926 'success': SUCCESS_STDERR,
927 'requirements': [],
928 },
929 {
930 'test_id': 1006,
931 'test_class': FioExeTest,
932 'exe': 't/strided.py',
5a36d0e4 933 'parameters': ['--fio', '{fio_path}'],
704cc4df
VF
934 'success': SUCCESS_DEFAULT,
935 'requirements': [],
936 },
937 {
938 'test_id': 1007,
939 'test_class': FioExeTest,
037b2b50
DF
940 'exe': 't/zbd/run-tests-against-nullb',
941 'parameters': ['-s', '1'],
704cc4df
VF
942 'success': SUCCESS_DEFAULT,
943 'requirements': [Requirements.linux, Requirements.zbd,
944 Requirements.root],
945 },
946 {
947 'test_id': 1008,
948 'test_class': FioExeTest,
037b2b50
DF
949 'exe': 't/zbd/run-tests-against-nullb',
950 'parameters': ['-s', '2'],
704cc4df
VF
951 'success': SUCCESS_DEFAULT,
952 'requirements': [Requirements.linux, Requirements.zbd,
953 Requirements.root, Requirements.zoned_nullb],
954 },
955 {
956 'test_id': 1009,
957 'test_class': FioExeTest,
958 'exe': 'unittests/unittest',
959 'parameters': None,
960 'success': SUCCESS_DEFAULT,
961 'requirements': [Requirements.unittests],
962 },
963 {
964 'test_id': 1010,
965 'test_class': FioExeTest,
966 'exe': 't/latency_percentiles.py',
967 'parameters': ['-f', '{fio_path}'],
968 'success': SUCCESS_DEFAULT,
969 'requirements': [],
970 },
8403eca6
VF
971 {
972 'test_id': 1011,
973 'test_class': FioExeTest,
974 'exe': 't/jsonplus2csv_test.py',
975 'parameters': ['-f', '{fio_path}'],
976 'success': SUCCESS_DEFAULT,
977 'requirements': [],
978 },
03900b0b 979 {
980 'test_id': 1012,
981 'test_class': FioExeTest,
982 'exe': 't/log_compression.py',
983 'parameters': ['-f', '{fio_path}'],
984 'success': SUCCESS_DEFAULT,
985 'requirements': [],
986 },
c994fa62
VF
987 {
988 'test_id': 1013,
989 'test_class': FioExeTest,
990 'exe': 't/random_seed.py',
991 'parameters': ['-f', '{fio_path}'],
992 'success': SUCCESS_DEFAULT,
993 'requirements': [],
994 },
2128f93a
VF
995 {
996 'test_id': 1014,
997 'test_class': FioExeTest,
998 'exe': 't/nvmept.py',
999 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'],
1000 'success': SUCCESS_DEFAULT,
2d0debb3
VF
1001 'requirements': [Requirements.linux, Requirements.nvmecdev],
1002 },
1003 {
1004 'test_id': 1015,
1005 'test_class': FioExeTest,
1006 'exe': 't/nvmept_trim.py',
1007 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'],
1008 'success': SUCCESS_DEFAULT,
2128f93a
VF
1009 'requirements': [Requirements.linux, Requirements.nvmecdev],
1010 },
df1eaa36
VF
1011]
1012
1013
1014def parse_args():
704cc4df
VF
1015 """Parse command-line arguments."""
1016
df1eaa36
VF
1017 parser = argparse.ArgumentParser()
1018 parser.add_argument('-r', '--fio-root',
1019 help='fio root path')
1020 parser.add_argument('-f', '--fio',
1021 help='path to fio executable (e.g., ./fio)')
1022 parser.add_argument('-a', '--artifact-root',
1023 help='artifact root directory')
1024 parser.add_argument('-s', '--skip', nargs='+', type=int,
1025 help='list of test(s) to skip')
1026 parser.add_argument('-o', '--run-only', nargs='+', type=int,
1027 help='list of test(s) to run, skipping all others')
6d5470c3
VF
1028 parser.add_argument('-d', '--debug', action='store_true',
1029 help='provide debug output')
b1bc705e
VF
1030 parser.add_argument('-k', '--skip-req', action='store_true',
1031 help='skip requirements checking')
58a77d2a
VF
1032 parser.add_argument('-p', '--pass-through', action='append',
1033 help='pass-through an argument to an executable test')
2128f93a
VF
1034 parser.add_argument('--nvmecdev', action='store', default=None,
1035 help='NVMe character device for **DESTRUCTIVE** testing (e.g., /dev/ng0n1)')
df1eaa36
VF
1036 args = parser.parse_args()
1037
1038 return args
1039
1040
1041def main():
704cc4df
VF
1042 """Entry point."""
1043
df1eaa36 1044 args = parse_args()
6d5470c3
VF
1045 if args.debug:
1046 logging.basicConfig(level=logging.DEBUG)
1047 else:
1048 logging.basicConfig(level=logging.INFO)
1049
58a77d2a
VF
1050 pass_through = {}
1051 if args.pass_through:
1052 for arg in args.pass_through:
1053 if not ':' in arg:
1b4ba547 1054 print(f"Invalid --pass-through argument '{arg}'")
58a77d2a
VF
1055 print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
1056 return
061a0773 1057 split = arg.split(":", 1)
58a77d2a 1058 pass_through[int(split[0])] = split[1]
061a0773 1059 logging.debug("Pass-through arguments: %s", pass_through)
58a77d2a 1060
df1eaa36
VF
1061 if args.fio_root:
1062 fio_root = args.fio_root
1063 else:
6d5470c3 1064 fio_root = str(Path(__file__).absolute().parent.parent)
1b4ba547 1065 print(f"fio root is {fio_root}")
df1eaa36
VF
1066
1067 if args.fio:
1068 fio_path = args.fio
1069 else:
742b8799
VF
1070 if platform.system() == "Windows":
1071 fio_exe = "fio.exe"
1072 else:
1073 fio_exe = "fio"
1074 fio_path = os.path.join(fio_root, fio_exe)
1b4ba547 1075 print(f"fio path is {fio_path}")
6d5470c3
VF
1076 if not shutil.which(fio_path):
1077 print("Warning: fio executable not found")
df1eaa36
VF
1078
1079 artifact_root = args.artifact_root if args.artifact_root else \
1b4ba547 1080 f"fio-test-{time.strftime('%Y%m%d-%H%M%S')}"
df1eaa36 1081 os.mkdir(artifact_root)
1b4ba547 1082 print(f"Artifact directory is {artifact_root}")
df1eaa36 1083
b1bc705e 1084 if not args.skip_req:
fb551941
VF
1085 Requirements(fio_root, args)
1086
1087 test_env = {
1088 'fio_path': fio_path,
1089 'fio_root': fio_root,
1090 'artifact_root': artifact_root,
1091 'pass_through': pass_through,
1092 }
1093 _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
df1eaa36
VF
1094 sys.exit(failed)
1095
1096
1097if __name__ == '__main__':
1098 main()