docs: change listed type for log_window_value to str
[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
0dc6e911 545class FioJobFileTest_iops_rate(FioJobFileTest):
12b609b5 546 """Test consists of fio test job t0011
df1eaa36
VF
547 Confirm that job0 iops == 1000
548 and that job1_iops / job0_iops ~ 8
549 With two runs of fio-3.16 I observed a ratio of 8.3"""
550
551 def check_result(self):
1b4ba547 552 super().check_result()
df1eaa36
VF
553
554 if not self.passed:
555 return
556
557 iops1 = self.json_data['jobs'][0]['read']['iops']
7ddc4ed1 558 logging.debug("Test %d: iops1: %f", self.testnum, iops1)
df1eaa36 559 iops2 = self.json_data['jobs'][1]['read']['iops']
7ddc4ed1 560 logging.debug("Test %d: iops2: %f", self.testnum, iops2)
df1eaa36 561 ratio = iops2 / iops1
704cc4df 562 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 563
0a9b8988 564 if iops1 < 950 or iops1 > 1050:
1b4ba547 565 self.failure_reason = f"{self.failure_reason} iops value mismatch,"
df1eaa36
VF
566 self.passed = False
567
d4e74fda 568 if ratio < 6 or ratio > 10:
1b4ba547 569 self.failure_reason = f"{self.failure_reason} iops ratio mismatch,"
df1eaa36
VF
570 self.passed = False
571
572
df1eaa36 573TEST_LIST = [
704cc4df
VF
574 {
575 'test_id': 1,
0dc6e911 576 'test_class': FioJobFileTest,
704cc4df
VF
577 'job': 't0001-52c58027.fio',
578 'success': SUCCESS_DEFAULT,
579 'pre_job': None,
580 'pre_success': None,
581 'requirements': [],
582 },
583 {
584 'test_id': 2,
0dc6e911 585 'test_class': FioJobFileTest,
704cc4df
VF
586 'job': 't0002-13af05ae-post.fio',
587 'success': SUCCESS_DEFAULT,
588 'pre_job': 't0002-13af05ae-pre.fio',
589 'pre_success': None,
590 'requirements': [Requirements.linux, Requirements.libaio],
591 },
592 {
593 'test_id': 3,
0dc6e911 594 'test_class': FioJobFileTest,
704cc4df
VF
595 'job': 't0003-0ae2c6e1-post.fio',
596 'success': SUCCESS_NONZERO,
597 'pre_job': 't0003-0ae2c6e1-pre.fio',
598 'pre_success': SUCCESS_DEFAULT,
599 'requirements': [Requirements.linux, Requirements.libaio],
600 },
601 {
602 'test_id': 4,
0dc6e911 603 'test_class': FioJobFileTest,
704cc4df
VF
604 'job': 't0004-8a99fdf6.fio',
605 'success': SUCCESS_DEFAULT,
606 'pre_job': None,
607 'pre_success': None,
608 'requirements': [Requirements.linux, Requirements.libaio],
609 },
610 {
611 'test_id': 5,
0dc6e911 612 'test_class': FioJobFileTest_t0005,
704cc4df
VF
613 'job': 't0005-f7078f7b.fio',
614 'success': SUCCESS_DEFAULT,
615 'pre_job': None,
616 'pre_success': None,
617 'output_format': 'json',
618 'requirements': [Requirements.not_windows],
619 },
620 {
621 'test_id': 6,
0dc6e911 622 'test_class': FioJobFileTest_t0006,
704cc4df
VF
623 'job': 't0006-82af2a7c.fio',
624 'success': SUCCESS_DEFAULT,
625 'pre_job': None,
626 'pre_success': None,
627 'output_format': 'json',
628 'requirements': [Requirements.linux, Requirements.libaio],
629 },
630 {
631 'test_id': 7,
0dc6e911 632 'test_class': FioJobFileTest_t0007,
704cc4df
VF
633 'job': 't0007-37cf9e3c.fio',
634 'success': SUCCESS_DEFAULT,
635 'pre_job': None,
636 'pre_success': None,
637 'output_format': 'json',
638 'requirements': [],
639 },
640 {
641 'test_id': 8,
0dc6e911 642 'test_class': FioJobFileTest_t0008,
704cc4df
VF
643 'job': 't0008-ae2fafc8.fio',
644 'success': SUCCESS_DEFAULT,
645 'pre_job': None,
646 'pre_success': None,
647 'output_format': 'json',
648 'requirements': [],
649 },
650 {
651 'test_id': 9,
0dc6e911 652 'test_class': FioJobFileTest_t0009,
704cc4df
VF
653 'job': 't0009-f8b0bd10.fio',
654 'success': SUCCESS_DEFAULT,
655 'pre_job': None,
656 'pre_success': None,
657 'output_format': 'json',
658 'requirements': [Requirements.not_macos,
659 Requirements.cpucount4],
660 # mac os does not support CPU affinity
661 },
662 {
663 'test_id': 10,
0dc6e911 664 'test_class': FioJobFileTest,
704cc4df
VF
665 'job': 't0010-b7aae4ba.fio',
666 'success': SUCCESS_DEFAULT,
667 'pre_job': None,
668 'pre_success': None,
669 'requirements': [],
670 },
671 {
672 'test_id': 11,
0dc6e911 673 'test_class': FioJobFileTest_iops_rate,
704cc4df
VF
674 'job': 't0011-5d2788d5.fio',
675 'success': SUCCESS_DEFAULT,
676 'pre_job': None,
677 'pre_success': None,
678 'output_format': 'json',
679 'requirements': [],
680 },
0a602473
BVA
681 {
682 'test_id': 12,
0dc6e911 683 'test_class': FioJobFileTest_t0012,
0a602473
BVA
684 'job': 't0012.fio',
685 'success': SUCCESS_DEFAULT,
686 'pre_job': None,
687 'pre_success': None,
688 'output_format': 'json',
d4e74fda 689 'requirements': [],
0a602473 690 },
f0c7ae7a
BVA
691 {
692 'test_id': 13,
0dc6e911 693 'test_class': FioJobFileTest,
f0c7ae7a
BVA
694 'job': 't0013.fio',
695 'success': SUCCESS_DEFAULT,
696 'pre_job': None,
697 'pre_success': None,
698 'output_format': 'json',
699 'requirements': [],
700 },
d4e74fda
DB
701 {
702 'test_id': 14,
0dc6e911 703 'test_class': FioJobFileTest_t0014,
d4e74fda
DB
704 'job': 't0014.fio',
705 'success': SUCCESS_DEFAULT,
706 'pre_job': None,
707 'pre_success': None,
708 'output_format': 'json',
709 'requirements': [],
710 },
60ebb939
VF
711 {
712 'test_id': 15,
0dc6e911 713 'test_class': FioJobFileTest_t0015,
60ebb939
VF
714 'job': 't0015-e78980ff.fio',
715 'success': SUCCESS_DEFAULT,
716 'pre_job': None,
717 'pre_success': None,
718 'output_format': 'json',
719 'requirements': [Requirements.linux, Requirements.libaio],
720 },
de31fe9a
VF
721 {
722 'test_id': 16,
0dc6e911 723 'test_class': FioJobFileTest_t0015,
f045d5f8 724 'job': 't0016-d54ae22.fio',
de31fe9a
VF
725 'success': SUCCESS_DEFAULT,
726 'pre_job': None,
727 'pre_success': None,
728 'output_format': 'json',
729 'requirements': [],
730 },
31a58cba
VF
731 {
732 'test_id': 17,
0dc6e911 733 'test_class': FioJobFileTest_t0015,
31a58cba
VF
734 'job': 't0017.fio',
735 'success': SUCCESS_DEFAULT,
736 'pre_job': None,
737 'pre_success': None,
738 'output_format': 'json',
739 'requirements': [Requirements.not_windows],
740 },
a2947c33
VF
741 {
742 'test_id': 18,
0dc6e911 743 'test_class': FioJobFileTest,
a2947c33
VF
744 'job': 't0018.fio',
745 'success': SUCCESS_DEFAULT,
746 'pre_job': None,
747 'pre_success': None,
748 'requirements': [Requirements.linux, Requirements.io_uring],
749 },
ef54f290
VF
750 {
751 'test_id': 19,
0dc6e911 752 'test_class': FioJobFileTest_t0019,
ef54f290
VF
753 'job': 't0019.fio',
754 'success': SUCCESS_DEFAULT,
755 'pre_job': None,
756 'pre_success': None,
757 'requirements': [],
758 },
759 {
760 'test_id': 20,
0dc6e911 761 'test_class': FioJobFileTest_t0020,
ef54f290
VF
762 'job': 't0020.fio',
763 'success': SUCCESS_DEFAULT,
764 'pre_job': None,
765 'pre_success': None,
766 'requirements': [],
767 },
eb40b275
VF
768 {
769 'test_id': 21,
0dc6e911 770 'test_class': FioJobFileTest_t0020,
eb40b275
VF
771 'job': 't0021.fio',
772 'success': SUCCESS_DEFAULT,
773 'pre_job': None,
774 'pre_success': None,
775 'requirements': [],
776 },
777 {
778 'test_id': 22,
0dc6e911 779 'test_class': FioJobFileTest_t0022,
eb40b275
VF
780 'job': 't0022.fio',
781 'success': SUCCESS_DEFAULT,
782 'pre_job': None,
783 'pre_success': None,
784 'requirements': [],
785 },
c37183f8
VF
786 {
787 'test_id': 23,
0dc6e911 788 'test_class': FioJobFileTest_t0023,
c37183f8
VF
789 'job': 't0023.fio',
790 'success': SUCCESS_DEFAULT,
791 'pre_job': None,
792 'pre_success': None,
793 'requirements': [],
794 },
2d5c4600
VF
795 {
796 'test_id': 24,
0dc6e911 797 'test_class': FioJobFileTest_t0024,
2d5c4600
VF
798 'job': 't0024.fio',
799 'success': SUCCESS_DEFAULT,
800 'pre_job': None,
801 'pre_success': None,
802 'requirements': [],
803 },
d661fb18
SK
804 {
805 'test_id': 25,
0dc6e911 806 'test_class': FioJobFileTest_t0025,
d661fb18
SK
807 'job': 't0025.fio',
808 'success': SUCCESS_DEFAULT,
809 'pre_job': None,
810 'pre_success': None,
811 'output_format': 'json',
812 'requirements': [],
813 },
c4704c08
SK
814 {
815 'test_id': 26,
0dc6e911 816 'test_class': FioJobFileTest,
c4704c08
SK
817 'job': 't0026.fio',
818 'success': SUCCESS_DEFAULT,
819 'pre_job': None,
820 'pre_success': None,
821 'requirements': [Requirements.not_windows],
822 },
ede04c27
LG
823 {
824 'test_id': 27,
0dc6e911 825 'test_class': FioJobFileTest_t0027,
ede04c27
LG
826 'job': 't0027.fio',
827 'success': SUCCESS_DEFAULT,
828 'pre_job': None,
829 'pre_success': None,
830 'requirements': [],
831 },
23fb1642
VF
832 {
833 'test_id': 28,
0dc6e911 834 'test_class': FioJobFileTest,
23fb1642
VF
835 'job': 't0028-c6cade16.fio',
836 'success': SUCCESS_DEFAULT,
837 'pre_job': None,
838 'pre_success': None,
839 'requirements': [],
840 },
704cc4df
VF
841 {
842 'test_id': 1000,
843 'test_class': FioExeTest,
844 'exe': 't/axmap',
845 'parameters': None,
846 'success': SUCCESS_DEFAULT,
847 'requirements': [],
848 },
849 {
850 'test_id': 1001,
851 'test_class': FioExeTest,
852 'exe': 't/ieee754',
853 'parameters': None,
854 'success': SUCCESS_DEFAULT,
855 'requirements': [],
856 },
857 {
858 'test_id': 1002,
859 'test_class': FioExeTest,
860 'exe': 't/lfsr-test',
861 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
862 'success': SUCCESS_STDERR,
863 'requirements': [],
864 },
865 {
866 'test_id': 1003,
867 'test_class': FioExeTest,
868 'exe': 't/readonly.py',
869 'parameters': ['-f', '{fio_path}'],
870 'success': SUCCESS_DEFAULT,
871 'requirements': [],
872 },
873 {
874 'test_id': 1004,
875 'test_class': FioExeTest,
876 'exe': 't/steadystate_tests.py',
877 'parameters': ['{fio_path}'],
878 'success': SUCCESS_DEFAULT,
879 'requirements': [],
880 },
881 {
882 'test_id': 1005,
883 'test_class': FioExeTest,
884 'exe': 't/stest',
885 'parameters': None,
886 'success': SUCCESS_STDERR,
887 'requirements': [],
888 },
889 {
890 'test_id': 1006,
891 'test_class': FioExeTest,
892 'exe': 't/strided.py',
5a36d0e4 893 'parameters': ['--fio', '{fio_path}'],
704cc4df
VF
894 'success': SUCCESS_DEFAULT,
895 'requirements': [],
896 },
897 {
898 'test_id': 1007,
899 'test_class': FioExeTest,
037b2b50
DF
900 'exe': 't/zbd/run-tests-against-nullb',
901 'parameters': ['-s', '1'],
704cc4df
VF
902 'success': SUCCESS_DEFAULT,
903 'requirements': [Requirements.linux, Requirements.zbd,
904 Requirements.root],
905 },
906 {
907 'test_id': 1008,
908 'test_class': FioExeTest,
037b2b50
DF
909 'exe': 't/zbd/run-tests-against-nullb',
910 'parameters': ['-s', '2'],
704cc4df
VF
911 'success': SUCCESS_DEFAULT,
912 'requirements': [Requirements.linux, Requirements.zbd,
913 Requirements.root, Requirements.zoned_nullb],
914 },
915 {
916 'test_id': 1009,
917 'test_class': FioExeTest,
918 'exe': 'unittests/unittest',
919 'parameters': None,
920 'success': SUCCESS_DEFAULT,
921 'requirements': [Requirements.unittests],
922 },
923 {
924 'test_id': 1010,
925 'test_class': FioExeTest,
926 'exe': 't/latency_percentiles.py',
927 'parameters': ['-f', '{fio_path}'],
928 'success': SUCCESS_DEFAULT,
929 'requirements': [],
930 },
8403eca6
VF
931 {
932 'test_id': 1011,
933 'test_class': FioExeTest,
934 'exe': 't/jsonplus2csv_test.py',
935 'parameters': ['-f', '{fio_path}'],
936 'success': SUCCESS_DEFAULT,
937 'requirements': [],
938 },
03900b0b 939 {
940 'test_id': 1012,
941 'test_class': FioExeTest,
942 'exe': 't/log_compression.py',
943 'parameters': ['-f', '{fio_path}'],
944 'success': SUCCESS_DEFAULT,
945 'requirements': [],
946 },
c994fa62
VF
947 {
948 'test_id': 1013,
949 'test_class': FioExeTest,
950 'exe': 't/random_seed.py',
951 'parameters': ['-f', '{fio_path}'],
952 'success': SUCCESS_DEFAULT,
953 'requirements': [],
954 },
2128f93a
VF
955 {
956 'test_id': 1014,
957 'test_class': FioExeTest,
958 'exe': 't/nvmept.py',
959 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'],
960 'success': SUCCESS_DEFAULT,
961 'requirements': [Requirements.linux, Requirements.nvmecdev],
962 },
df1eaa36
VF
963]
964
965
966def parse_args():
704cc4df
VF
967 """Parse command-line arguments."""
968
df1eaa36
VF
969 parser = argparse.ArgumentParser()
970 parser.add_argument('-r', '--fio-root',
971 help='fio root path')
972 parser.add_argument('-f', '--fio',
973 help='path to fio executable (e.g., ./fio)')
974 parser.add_argument('-a', '--artifact-root',
975 help='artifact root directory')
976 parser.add_argument('-s', '--skip', nargs='+', type=int,
977 help='list of test(s) to skip')
978 parser.add_argument('-o', '--run-only', nargs='+', type=int,
979 help='list of test(s) to run, skipping all others')
6d5470c3
VF
980 parser.add_argument('-d', '--debug', action='store_true',
981 help='provide debug output')
b1bc705e
VF
982 parser.add_argument('-k', '--skip-req', action='store_true',
983 help='skip requirements checking')
58a77d2a
VF
984 parser.add_argument('-p', '--pass-through', action='append',
985 help='pass-through an argument to an executable test')
2128f93a
VF
986 parser.add_argument('--nvmecdev', action='store', default=None,
987 help='NVMe character device for **DESTRUCTIVE** testing (e.g., /dev/ng0n1)')
df1eaa36
VF
988 args = parser.parse_args()
989
990 return args
991
992
993def main():
704cc4df
VF
994 """Entry point."""
995
df1eaa36 996 args = parse_args()
6d5470c3
VF
997 if args.debug:
998 logging.basicConfig(level=logging.DEBUG)
999 else:
1000 logging.basicConfig(level=logging.INFO)
1001
58a77d2a
VF
1002 pass_through = {}
1003 if args.pass_through:
1004 for arg in args.pass_through:
1005 if not ':' in arg:
1b4ba547 1006 print(f"Invalid --pass-through argument '{arg}'")
58a77d2a
VF
1007 print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
1008 return
061a0773 1009 split = arg.split(":", 1)
58a77d2a 1010 pass_through[int(split[0])] = split[1]
061a0773 1011 logging.debug("Pass-through arguments: %s", pass_through)
58a77d2a 1012
df1eaa36
VF
1013 if args.fio_root:
1014 fio_root = args.fio_root
1015 else:
6d5470c3 1016 fio_root = str(Path(__file__).absolute().parent.parent)
1b4ba547 1017 print(f"fio root is {fio_root}")
df1eaa36
VF
1018
1019 if args.fio:
1020 fio_path = args.fio
1021 else:
742b8799
VF
1022 if platform.system() == "Windows":
1023 fio_exe = "fio.exe"
1024 else:
1025 fio_exe = "fio"
1026 fio_path = os.path.join(fio_root, fio_exe)
1b4ba547 1027 print(f"fio path is {fio_path}")
6d5470c3
VF
1028 if not shutil.which(fio_path):
1029 print("Warning: fio executable not found")
df1eaa36
VF
1030
1031 artifact_root = args.artifact_root if args.artifact_root else \
1b4ba547 1032 f"fio-test-{time.strftime('%Y%m%d-%H%M%S')}"
df1eaa36 1033 os.mkdir(artifact_root)
1b4ba547 1034 print(f"Artifact directory is {artifact_root}")
df1eaa36 1035
b1bc705e 1036 if not args.skip_req:
fb551941
VF
1037 Requirements(fio_root, args)
1038
1039 test_env = {
1040 'fio_path': fio_path,
1041 'fio_root': fio_root,
1042 'artifact_root': artifact_root,
1043 'pass_through': pass_through,
1044 }
1045 _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
df1eaa36
VF
1046 sys.exit(failed)
1047
1048
1049if __name__ == '__main__':
1050 main()