test: add test for lib/pattern segfault issue
[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
46import json
47import time
6d5470c3 48import shutil
df1eaa36
VF
49import logging
50import argparse
b1bc705e 51import platform
aa9f2627 52import traceback
df1eaa36 53import subprocess
b1bc705e 54import multiprocessing
df1eaa36
VF
55from pathlib import Path
56
57
114eadb2 58class FioTest():
df1eaa36
VF
59 """Base for all fio tests."""
60
61 def __init__(self, exe_path, parameters, success):
62 self.exe_path = exe_path
63 self.parameters = parameters
64 self.success = success
65 self.output = {}
66 self.artifact_root = None
67 self.testnum = None
68 self.test_dir = None
69 self.passed = True
70 self.failure_reason = ''
704cc4df
VF
71 self.command_file = None
72 self.stdout_file = None
73 self.stderr_file = None
74 self.exitcode_file = None
df1eaa36
VF
75
76 def setup(self, artifact_root, testnum):
704cc4df
VF
77 """Setup instance variables for test."""
78
df1eaa36
VF
79 self.artifact_root = artifact_root
80 self.testnum = testnum
81 self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum))
82 if not os.path.exists(self.test_dir):
83 os.mkdir(self.test_dir)
84
85 self.command_file = os.path.join(
704cc4df
VF
86 self.test_dir,
87 "{0}.command".format(os.path.basename(self.exe_path)))
df1eaa36 88 self.stdout_file = os.path.join(
704cc4df
VF
89 self.test_dir,
90 "{0}.stdout".format(os.path.basename(self.exe_path)))
df1eaa36 91 self.stderr_file = os.path.join(
704cc4df
VF
92 self.test_dir,
93 "{0}.stderr".format(os.path.basename(self.exe_path)))
94 self.exitcode_file = os.path.join(
95 self.test_dir,
96 "{0}.exitcode".format(os.path.basename(self.exe_path)))
df1eaa36
VF
97
98 def run(self):
704cc4df
VF
99 """Run the test."""
100
df1eaa36
VF
101 raise NotImplementedError()
102
103 def check_result(self):
704cc4df
VF
104 """Check test results."""
105
df1eaa36
VF
106 raise NotImplementedError()
107
108
109class FioExeTest(FioTest):
110 """Test consists of an executable binary or script"""
111
112 def __init__(self, exe_path, parameters, success):
113 """Construct a FioExeTest which is a FioTest consisting of an
114 executable binary or script.
115
116 exe_path: location of executable binary or script
117 parameters: list of parameters for executable
118 success: Definition of test success
119 """
120
121 FioTest.__init__(self, exe_path, parameters, success)
122
df1eaa36 123 def run(self):
704cc4df
VF
124 """Execute the binary or script described by this instance."""
125
58a77d2a 126 command = [self.exe_path] + self.parameters
df1eaa36
VF
127 command_file = open(self.command_file, "w+")
128 command_file.write("%s\n" % command)
129 command_file.close()
130
131 stdout_file = open(self.stdout_file, "w+")
132 stderr_file = open(self.stderr_file, "w+")
704cc4df 133 exitcode_file = open(self.exitcode_file, "w+")
df1eaa36 134 try:
b048455f 135 proc = None
df1eaa36
VF
136 # Avoid using subprocess.run() here because when a timeout occurs,
137 # fio will be stopped with SIGKILL. This does not give fio a
138 # chance to clean up and means that child processes may continue
139 # running and submitting IO.
140 proc = subprocess.Popen(command,
141 stdout=stdout_file,
142 stderr=stderr_file,
143 cwd=self.test_dir,
144 universal_newlines=True)
145 proc.communicate(timeout=self.success['timeout'])
704cc4df
VF
146 exitcode_file.write('{0}\n'.format(proc.returncode))
147 logging.debug("Test %d: return code: %d", self.testnum, proc.returncode)
df1eaa36
VF
148 self.output['proc'] = proc
149 except subprocess.TimeoutExpired:
150 proc.terminate()
151 proc.communicate()
152 assert proc.poll()
153 self.output['failure'] = 'timeout'
154 except Exception:
b048455f
VF
155 if proc:
156 if not proc.poll():
157 proc.terminate()
158 proc.communicate()
df1eaa36
VF
159 self.output['failure'] = 'exception'
160 self.output['exc_info'] = sys.exc_info()
161 finally:
162 stdout_file.close()
163 stderr_file.close()
704cc4df 164 exitcode_file.close()
df1eaa36
VF
165
166 def check_result(self):
704cc4df
VF
167 """Check results of test run."""
168
df1eaa36
VF
169 if 'proc' not in self.output:
170 if self.output['failure'] == 'timeout':
171 self.failure_reason = "{0} timeout,".format(self.failure_reason)
172 else:
173 assert self.output['failure'] == 'exception'
174 self.failure_reason = '{0} exception: {1}, {2}'.format(
704cc4df
VF
175 self.failure_reason, self.output['exc_info'][0],
176 self.output['exc_info'][1])
df1eaa36
VF
177
178 self.passed = False
179 return
180
181 if 'zero_return' in self.success:
182 if self.success['zero_return']:
183 if self.output['proc'].returncode != 0:
184 self.passed = False
185 self.failure_reason = "{0} non-zero return code,".format(self.failure_reason)
186 else:
187 if self.output['proc'].returncode == 0:
188 self.failure_reason = "{0} zero return code,".format(self.failure_reason)
189 self.passed = False
190
6d5470c3 191 stderr_size = os.path.getsize(self.stderr_file)
df1eaa36 192 if 'stderr_empty' in self.success:
df1eaa36
VF
193 if self.success['stderr_empty']:
194 if stderr_size != 0:
195 self.failure_reason = "{0} stderr not empty,".format(self.failure_reason)
196 self.passed = False
197 else:
198 if stderr_size == 0:
199 self.failure_reason = "{0} stderr empty,".format(self.failure_reason)
200 self.passed = False
201
202
203class FioJobTest(FioExeTest):
204 """Test consists of a fio job"""
205
206 def __init__(self, fio_path, fio_job, success, fio_pre_job=None,
207 fio_pre_success=None, output_format="normal"):
208 """Construct a FioJobTest which is a FioExeTest consisting of a
209 single fio job file with an optional setup step.
210
211 fio_path: location of fio executable
212 fio_job: location of fio job file
213 success: Definition of test success
214 fio_pre_job: fio job for preconditioning
215 fio_pre_success: Definition of test success for fio precon job
216 output_format: normal (default), json, jsonplus, or terse
217 """
218
219 self.fio_job = fio_job
220 self.fio_pre_job = fio_pre_job
221 self.fio_pre_success = fio_pre_success if fio_pre_success else success
222 self.output_format = output_format
223 self.precon_failed = False
224 self.json_data = None
225 self.fio_output = "{0}.output".format(os.path.basename(self.fio_job))
226 self.fio_args = [
771dbb52 227 "--max-jobs=16",
df1eaa36
VF
228 "--output-format={0}".format(self.output_format),
229 "--output={0}".format(self.fio_output),
230 self.fio_job,
231 ]
232 FioExeTest.__init__(self, fio_path, self.fio_args, success)
233
234 def setup(self, artifact_root, testnum):
704cc4df
VF
235 """Setup instance variables for fio job test."""
236
df1eaa36
VF
237 super(FioJobTest, self).setup(artifact_root, testnum)
238
239 self.command_file = os.path.join(
704cc4df
VF
240 self.test_dir,
241 "{0}.command".format(os.path.basename(self.fio_job)))
df1eaa36 242 self.stdout_file = os.path.join(
704cc4df
VF
243 self.test_dir,
244 "{0}.stdout".format(os.path.basename(self.fio_job)))
df1eaa36 245 self.stderr_file = os.path.join(
704cc4df
VF
246 self.test_dir,
247 "{0}.stderr".format(os.path.basename(self.fio_job)))
248 self.exitcode_file = os.path.join(
249 self.test_dir,
250 "{0}.exitcode".format(os.path.basename(self.fio_job)))
df1eaa36
VF
251
252 def run_pre_job(self):
704cc4df
VF
253 """Run fio job precondition step."""
254
df1eaa36
VF
255 precon = FioJobTest(self.exe_path, self.fio_pre_job,
256 self.fio_pre_success,
257 output_format=self.output_format)
258 precon.setup(self.artifact_root, self.testnum)
259 precon.run()
260 precon.check_result()
261 self.precon_failed = not precon.passed
262 self.failure_reason = precon.failure_reason
263
264 def run(self):
704cc4df
VF
265 """Run fio job test."""
266
df1eaa36
VF
267 if self.fio_pre_job:
268 self.run_pre_job()
269
270 if not self.precon_failed:
271 super(FioJobTest, self).run()
272 else:
704cc4df 273 logging.debug("Test %d: precondition step failed", self.testnum)
df1eaa36 274
15a73987
VF
275 @classmethod
276 def get_file(cls, filename):
277 """Safely read a file."""
278 file_data = ''
279 success = True
280
281 try:
282 with open(filename, "r") as output_file:
283 file_data = output_file.read()
284 except OSError:
285 success = False
286
287 return file_data, success
288
0f5234b4
VF
289 def get_file_fail(self, filename):
290 """Safely read a file and fail the test upon error."""
291 file_data = None
292
293 try:
294 with open(filename, "r") as output_file:
295 file_data = output_file.read()
296 except OSError:
297 self.failure_reason += " unable to read file {0}".format(filename)
298 self.passed = False
299
300 return file_data
301
df1eaa36 302 def check_result(self):
704cc4df
VF
303 """Check fio job results."""
304
df1eaa36
VF
305 if self.precon_failed:
306 self.passed = False
307 self.failure_reason = "{0} precondition step failed,".format(self.failure_reason)
308 return
309
310 super(FioJobTest, self).check_result()
311
6d5470c3
VF
312 if not self.passed:
313 return
314
704cc4df 315 if 'json' not in self.output_format:
742b8799
VF
316 return
317
0f5234b4
VF
318 file_data = self.get_file_fail(os.path.join(self.test_dir, self.fio_output))
319 if not file_data:
742b8799
VF
320 return
321
322 #
323 # Sometimes fio informational messages are included at the top of the
324 # JSON output, especially under Windows. Try to decode output as JSON
db2637ec 325 # data, skipping everything until the first {
742b8799
VF
326 #
327 lines = file_data.splitlines()
db2637ec
VF
328 file_data = '\n'.join(lines[lines.index("{"):])
329 try:
330 self.json_data = json.loads(file_data)
331 except json.JSONDecodeError:
332 self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason)
333 self.passed = False
df1eaa36
VF
334
335
336class FioJobTest_t0005(FioJobTest):
337 """Test consists of fio test job t0005
338 Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400"""
339
340 def check_result(self):
341 super(FioJobTest_t0005, self).check_result()
342
343 if not self.passed:
344 return
345
346 if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400:
347 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
348 self.passed = False
349 if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400:
350 self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
351 self.passed = False
352
353
354class FioJobTest_t0006(FioJobTest):
355 """Test consists of fio test job t0006
356 Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']"""
357
358 def check_result(self):
359 super(FioJobTest_t0006, self).check_result()
360
361 if not self.passed:
362 return
363
364 ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
365 / self.json_data['jobs'][0]['write']['io_kbytes']
704cc4df 366 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36
VF
367 if ratio < 1.99 or ratio > 2.01:
368 self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason)
369 self.passed = False
370
371
372class FioJobTest_t0007(FioJobTest):
373 """Test consists of fio test job t0007
374 Confirm that read['io_kbytes'] = 87040"""
375
376 def check_result(self):
377 super(FioJobTest_t0007, self).check_result()
378
379 if not self.passed:
380 return
381
382 if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040:
383 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
384 self.passed = False
385
386
387class FioJobTest_t0008(FioJobTest):
388 """Test consists of fio test job t0008
389 Confirm that read['io_kbytes'] = 32768 and that
77c758db 390 write['io_kbytes'] ~ 16384
df1eaa36 391
77c758db
VF
392 This is a 50/50 seq read/write workload. Since fio flips a coin to
393 determine whether to issue a read or a write, total bytes written will not
394 be exactly 16384K. But total bytes read will be exactly 32768K because
395 reads will include the initial phase as well as the verify phase where all
396 the blocks originally written will be read."""
df1eaa36
VF
397
398 def check_result(self):
399 super(FioJobTest_t0008, self).check_result()
400
401 if not self.passed:
402 return
403
77c758db 404 ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16384
704cc4df 405 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 406
77c758db 407 if ratio < 0.97 or ratio > 1.03:
df1eaa36
VF
408 self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
409 self.passed = False
410 if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768:
411 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
412 self.passed = False
413
414
415class FioJobTest_t0009(FioJobTest):
416 """Test consists of fio test job t0009
417 Confirm that runtime >= 60s"""
418
419 def check_result(self):
420 super(FioJobTest_t0009, self).check_result()
421
422 if not self.passed:
423 return
424
704cc4df 425 logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
df1eaa36
VF
426
427 if self.json_data['jobs'][0]['elapsed'] < 60:
428 self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason)
429 self.passed = False
430
431
d4e74fda
DB
432class FioJobTest_t0012(FioJobTest):
433 """Test consists of fio test job t0012
434 Confirm ratios of job iops are 1:5:10
435 job1,job2,job3 respectively"""
436
437 def check_result(self):
438 super(FioJobTest_t0012, self).check_result()
439
440 if not self.passed:
441 return
442
443 iops_files = []
114eadb2
VF
444 for i in range(1, 4):
445 filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(
446 self.fio_job), i))
0f5234b4
VF
447 file_data = self.get_file_fail(filename)
448 if not file_data:
d4e74fda
DB
449 return
450
451 iops_files.append(file_data.splitlines())
452
453 # there are 9 samples for job1 and job2, 4 samples for job3
454 iops1 = 0.0
455 iops2 = 0.0
456 iops3 = 0.0
457 for i in range(9):
458 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
459 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
460 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
461
462 ratio1 = iops3/iops2
463 ratio2 = iops3/iops1
114eadb2
VF
464 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
465 "job3/job2={4:.3f} job3/job1={5:.3f}".format(i, iops1, iops2, iops3, ratio1,
466 ratio2))
d4e74fda
DB
467
468 # test job1 and job2 succeeded to recalibrate
469 if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13:
114eadb2
VF
470 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
471 "expected r1~2 r2~10 got r1={3:.3f} r2={4:.3f},".format(iops1, iops2, iops3,
472 ratio1, ratio2)
d4e74fda
DB
473 self.passed = False
474 return
475
476
477class FioJobTest_t0014(FioJobTest):
478 """Test consists of fio test job t0014
479 Confirm that job1_iops / job2_iops ~ 1:2 for entire duration
480 and that job1_iops / job3_iops ~ 1:3 for first half of duration.
481
482 The test is about making sure the flow feature can
483 re-calibrate the activity dynamically"""
484
485 def check_result(self):
486 super(FioJobTest_t0014, self).check_result()
487
488 if not self.passed:
489 return
490
491 iops_files = []
114eadb2
VF
492 for i in range(1, 4):
493 filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(
494 self.fio_job), i))
0f5234b4
VF
495 file_data = self.get_file_fail(filename)
496 if not file_data:
d4e74fda
DB
497 return
498
499 iops_files.append(file_data.splitlines())
500
501 # there are 9 samples for job1 and job2, 4 samples for job3
502 iops1 = 0.0
503 iops2 = 0.0
504 iops3 = 0.0
505 for i in range(9):
506 if i < 4:
507 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
508 elif i == 4:
509 ratio1 = iops1 / iops2
510 ratio2 = iops1 / iops3
511
512
513 if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45:
114eadb2
VF
514 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
515 "expected r1~0.5 r2~0.33 got r1={3:.3f} r2={4:.3f},".format(
516 iops1, iops2, iops3, ratio1, ratio2)
d4e74fda
DB
517 self.passed = False
518
519 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
520 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
521
522 ratio1 = iops1/iops2
523 ratio2 = iops1/iops3
114eadb2
VF
524 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
525 "job1/job2={4:.3f} job1/job3={5:.3f}".format(i, iops1, iops2, iops3,
526 ratio1, ratio2))
d4e74fda
DB
527
528 # test job1 and job2 succeeded to recalibrate
529 if ratio1 < 0.43 or ratio1 > 0.57:
114eadb2
VF
530 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} expected ratio~0.5 " \
531 "got ratio={2:.3f},".format(iops1, iops2, ratio1)
d4e74fda
DB
532 self.passed = False
533 return
534
535
60ebb939 536class FioJobTest_t0015(FioJobTest):
de31fe9a 537 """Test consists of fio test jobs t0015 and t0016
60ebb939
VF
538 Confirm that mean(slat) + mean(clat) = mean(tlat)"""
539
540 def check_result(self):
541 super(FioJobTest_t0015, self).check_result()
542
543 if not self.passed:
544 return
545
546 slat = self.json_data['jobs'][0]['read']['slat_ns']['mean']
547 clat = self.json_data['jobs'][0]['read']['clat_ns']['mean']
548 tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean']
549 logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat)
550
551 if abs(slat + clat - tlat) > 1:
552 self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format(
553 self.failure_reason, slat, clat, slat+clat, tlat)
554 self.passed = False
555
556
ef54f290
VF
557class FioJobTest_t0019(FioJobTest):
558 """Test consists of fio test job t0019
559 Confirm that all offsets were touched sequentially"""
560
561 def check_result(self):
562 super(FioJobTest_t0019, self).check_result()
563
564 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
565 file_data = self.get_file_fail(bw_log_filename)
566 if not file_data:
114eadb2
VF
567 return
568
ef54f290
VF
569 log_lines = file_data.split('\n')
570
571 prev = -4096
572 for line in log_lines:
573 if len(line.strip()) == 0:
574 continue
575 cur = int(line.split(',')[4])
576 if cur - prev != 4096:
577 self.passed = False
578 self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur)
579 return
580 prev = cur
581
582 if cur/4096 != 255:
583 self.passed = False
584 self.failure_reason = "unexpected last offset {0}".format(cur)
585
586
587class FioJobTest_t0020(FioJobTest):
eb40b275 588 """Test consists of fio test jobs t0020 and t0021
ef54f290
VF
589 Confirm that almost all offsets were touched non-sequentially"""
590
591 def check_result(self):
592 super(FioJobTest_t0020, self).check_result()
593
594 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
595 file_data = self.get_file_fail(bw_log_filename)
596 if not file_data:
114eadb2
VF
597 return
598
ef54f290
VF
599 log_lines = file_data.split('\n')
600
601 seq_count = 0
602 offsets = set()
603
604 prev = int(log_lines[0].split(',')[4])
605 for line in log_lines[1:]:
606 offsets.add(prev/4096)
607 if len(line.strip()) == 0:
608 continue
609 cur = int(line.split(',')[4])
610 if cur - prev == 4096:
611 seq_count += 1
612 prev = cur
613
614 # 10 is an arbitrary threshold
615 if seq_count > 10:
616 self.passed = False
617 self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count)
618
619 if len(offsets) != 256:
620 self.passed = False
621 self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets))
622
623 for i in range(256):
624 if not i in offsets:
625 self.passed = False
626 self.failure_reason += " missing offset {0}".format(i*4096)
627
628
eb40b275
VF
629class FioJobTest_t0022(FioJobTest):
630 """Test consists of fio test job t0022"""
631
632 def check_result(self):
633 super(FioJobTest_t0022, self).check_result()
634
635 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
636 file_data = self.get_file_fail(bw_log_filename)
637 if not file_data:
114eadb2
VF
638 return
639
eb40b275
VF
640 log_lines = file_data.split('\n')
641
642 filesize = 1024*1024
643 bs = 4096
644 seq_count = 0
645 offsets = set()
646
647 prev = int(log_lines[0].split(',')[4])
648 for line in log_lines[1:]:
649 offsets.add(prev/bs)
650 if len(line.strip()) == 0:
651 continue
652 cur = int(line.split(',')[4])
653 if cur - prev == bs:
654 seq_count += 1
655 prev = cur
656
657 # 10 is an arbitrary threshold
658 if seq_count > 10:
659 self.passed = False
660 self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count)
661
662 if len(offsets) == filesize/bs:
663 self.passed = False
114eadb2 664 self.failure_reason += " no duplicate offsets found with norandommap=1"
eb40b275
VF
665
666
c37183f8 667class FioJobTest_t0023(FioJobTest):
21a20229 668 """Test consists of fio test job t0023 randtrimwrite test."""
c37183f8 669
1fb78294
VF
670 def check_trimwrite(self, filename):
671 """Make sure that trims are followed by writes of the same size at the same offset."""
672
c37183f8 673 bw_log_filename = os.path.join(self.test_dir, filename)
0f5234b4
VF
674 file_data = self.get_file_fail(bw_log_filename)
675 if not file_data:
114eadb2
VF
676 return
677
c37183f8
VF
678 log_lines = file_data.split('\n')
679
680 prev_ddir = 1
681 for line in log_lines:
682 if len(line.strip()) == 0:
683 continue
684 vals = line.split(',')
685 ddir = int(vals[2])
686 bs = int(vals[3])
687 offset = int(vals[4])
688 if prev_ddir == 1:
689 if ddir != 2:
690 self.passed = False
21a20229
VF
691 self.failure_reason += " {0}: write not preceeded by trim: {1}".format(
692 bw_log_filename, line)
c37183f8
VF
693 break
694 else:
695 if ddir != 1:
696 self.passed = False
21a20229
VF
697 self.failure_reason += " {0}: trim not preceeded by write: {1}".format(
698 bw_log_filename, line)
c37183f8
VF
699 break
700 else:
701 if prev_bs != bs:
702 self.passed = False
21a20229
VF
703 self.failure_reason += " {0}: block size does not match: {1}".format(
704 bw_log_filename, line)
c37183f8
VF
705 break
706 if prev_offset != offset:
707 self.passed = False
21a20229
VF
708 self.failure_reason += " {0}: offset does not match: {1}".format(
709 bw_log_filename, line)
c37183f8
VF
710 break
711 prev_ddir = ddir
712 prev_bs = bs
713 prev_offset = offset
714
715
9e83aa16 716 def check_all_offsets(self, filename, sectorsize, filesize):
21a20229
VF
717 """Make sure all offsets were touched."""
718
0f5234b4
VF
719 file_data = self.get_file_fail(os.path.join(self.test_dir, filename))
720 if not file_data:
9e83aa16
VF
721 return
722
723 log_lines = file_data.split('\n')
724
725 offsets = set()
726
727 for line in log_lines:
728 if len(line.strip()) == 0:
729 continue
730 vals = line.split(',')
731 bs = int(vals[3])
732 offset = int(vals[4])
733 if offset % sectorsize != 0:
734 self.passed = False
21a20229
VF
735 self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(
736 filename, offset, sectorsize)
737 break
9e83aa16
VF
738 if bs % sectorsize != 0:
739 self.passed = False
21a20229
VF
740 self.failure_reason += " {0}: block size {1} not a multiple of sector size " \
741 "{2}".format(filename, bs, sectorsize)
742 break
9e83aa16
VF
743 for i in range(int(bs/sectorsize)):
744 offsets.add(offset/sectorsize + i)
745
746 if len(offsets) != filesize/sectorsize:
747 self.passed = False
21a20229
VF
748 self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(
749 filename, len(offsets), filesize/sectorsize)
9e83aa16 750 else:
21a20229 751 logging.debug("%s: %d sectors touched", filename, len(offsets))
9e83aa16
VF
752
753
c37183f8
VF
754 def check_result(self):
755 super(FioJobTest_t0023, self).check_result()
756
9e83aa16
VF
757 filesize = 1024*1024
758
1fb78294
VF
759 self.check_trimwrite("basic_bw.log")
760 self.check_trimwrite("bs_bw.log")
761 self.check_trimwrite("bsrange_bw.log")
762 self.check_trimwrite("bssplit_bw.log")
763 self.check_trimwrite("basic_no_rm_bw.log")
764 self.check_trimwrite("bs_no_rm_bw.log")
765 self.check_trimwrite("bsrange_no_rm_bw.log")
766 self.check_trimwrite("bssplit_no_rm_bw.log")
c37183f8 767
9e83aa16
VF
768 self.check_all_offsets("basic_bw.log", 4096, filesize)
769 self.check_all_offsets("bs_bw.log", 8192, filesize)
770 self.check_all_offsets("bsrange_bw.log", 512, filesize)
771 self.check_all_offsets("bssplit_bw.log", 512, filesize)
c37183f8
VF
772
773
2d5c4600
VF
774class FioJobTest_t0024(FioJobTest_t0023):
775 """Test consists of fio test job t0024 trimwrite test."""
776
777 def check_result(self):
778 # call FioJobTest_t0023's parent to skip checks done by t0023
779 super(FioJobTest_t0023, self).check_result()
780
781 filesize = 1024*1024
782
783 self.check_trimwrite("basic_bw.log")
784 self.check_trimwrite("bs_bw.log")
785 self.check_trimwrite("bsrange_bw.log")
786 self.check_trimwrite("bssplit_bw.log")
787
788 self.check_all_offsets("basic_bw.log", 4096, filesize)
789 self.check_all_offsets("bs_bw.log", 8192, filesize)
790 self.check_all_offsets("bsrange_bw.log", 512, filesize)
791 self.check_all_offsets("bssplit_bw.log", 512, filesize)
792
793
d661fb18
SK
794class FioJobTest_t0025(FioJobTest):
795 """Test experimental verify read backs written data pattern."""
796 def check_result(self):
797 super(FioJobTest_t0025, self).check_result()
798
799 if not self.passed:
800 return
801
802 if self.json_data['jobs'][0]['read']['io_kbytes'] != 128:
803 self.passed = False
804
ede04c27
LG
805class FioJobTest_t0027(FioJobTest):
806 def setup(self, *args, **kws):
807 super(FioJobTest_t0027, self).setup(*args, **kws)
808 self.pattern_file = os.path.join(self.test_dir, "t0027.pattern")
809 self.output_file = os.path.join(self.test_dir, "t0027file")
810 self.pattern = os.urandom(16 << 10)
811 with open(self.pattern_file, "wb") as f:
812 f.write(self.pattern)
813
814 def check_result(self):
815 super(FioJobTest_t0027, self).check_result()
816
817 if not self.passed:
818 return
819
820 with open(self.output_file, "rb") as f:
821 data = f.read()
822
823 if data != self.pattern:
824 self.passed = False
d661fb18 825
0a602473 826class FioJobTest_iops_rate(FioJobTest):
df1eaa36
VF
827 """Test consists of fio test job t0009
828 Confirm that job0 iops == 1000
829 and that job1_iops / job0_iops ~ 8
830 With two runs of fio-3.16 I observed a ratio of 8.3"""
831
832 def check_result(self):
0a602473 833 super(FioJobTest_iops_rate, self).check_result()
df1eaa36
VF
834
835 if not self.passed:
836 return
837
838 iops1 = self.json_data['jobs'][0]['read']['iops']
7ddc4ed1 839 logging.debug("Test %d: iops1: %f", self.testnum, iops1)
df1eaa36 840 iops2 = self.json_data['jobs'][1]['read']['iops']
7ddc4ed1 841 logging.debug("Test %d: iops2: %f", self.testnum, iops2)
df1eaa36 842 ratio = iops2 / iops1
704cc4df 843 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 844
0a9b8988 845 if iops1 < 950 or iops1 > 1050:
df1eaa36
VF
846 self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason)
847 self.passed = False
848
d4e74fda 849 if ratio < 6 or ratio > 10:
df1eaa36
VF
850 self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason)
851 self.passed = False
852
853
114eadb2 854class Requirements():
b1bc705e
VF
855 """Requirements consists of multiple run environment characteristics.
856 These are to determine if a particular test can be run"""
857
858 _linux = False
859 _libaio = False
a2947c33 860 _io_uring = False
b1bc705e
VF
861 _zbd = False
862 _root = False
863 _zoned_nullb = False
864 _not_macos = False
c58b33b4 865 _not_windows = False
b1bc705e
VF
866 _unittests = False
867 _cpucount4 = False
868
869 def __init__(self, fio_root):
870 Requirements._not_macos = platform.system() != "Darwin"
c58b33b4 871 Requirements._not_windows = platform.system() != "Windows"
b1bc705e
VF
872 Requirements._linux = platform.system() == "Linux"
873
874 if Requirements._linux:
15a73987
VF
875 config_file = os.path.join(fio_root, "config-host.h")
876 contents, success = FioJobTest.get_file(config_file)
877 if not success:
b1bc705e
VF
878 print("Unable to open {0} to check requirements".format(config_file))
879 Requirements._zbd = True
880 else:
b7694961 881 Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents
b1bc705e
VF
882 Requirements._libaio = "CONFIG_LIBAIO" in contents
883
a2947c33
VF
884 contents, success = FioJobTest.get_file("/proc/kallsyms")
885 if not success:
886 print("Unable to open '/proc/kallsyms' to probe for io_uring support")
887 else:
888 Requirements._io_uring = "io_uring_setup" in contents
889
b1bc705e
VF
890 Requirements._root = (os.geteuid() == 0)
891 if Requirements._zbd and Requirements._root:
8854e368
VF
892 try:
893 subprocess.run(["modprobe", "null_blk"],
894 stdout=subprocess.PIPE,
895 stderr=subprocess.PIPE)
896 if os.path.exists("/sys/module/null_blk/parameters/zoned"):
897 Requirements._zoned_nullb = True
898 except Exception:
899 pass
b1bc705e 900
742b8799
VF
901 if platform.system() == "Windows":
902 utest_exe = "unittest.exe"
903 else:
904 utest_exe = "unittest"
905 unittest_path = os.path.join(fio_root, "unittests", utest_exe)
b1bc705e
VF
906 Requirements._unittests = os.path.exists(unittest_path)
907
908 Requirements._cpucount4 = multiprocessing.cpu_count() >= 4
909
910 req_list = [Requirements.linux,
911 Requirements.libaio,
a2947c33 912 Requirements.io_uring,
b1bc705e
VF
913 Requirements.zbd,
914 Requirements.root,
915 Requirements.zoned_nullb,
916 Requirements.not_macos,
c58b33b4 917 Requirements.not_windows,
b1bc705e
VF
918 Requirements.unittests,
919 Requirements.cpucount4]
920 for req in req_list:
921 value, desc = req()
704cc4df 922 logging.debug("Requirements: Requirement '%s' met? %s", desc, value)
b1bc705e 923
704cc4df
VF
924 @classmethod
925 def linux(cls):
926 """Are we running on Linux?"""
b1bc705e
VF
927 return Requirements._linux, "Linux required"
928
704cc4df
VF
929 @classmethod
930 def libaio(cls):
931 """Is libaio available?"""
b1bc705e
VF
932 return Requirements._libaio, "libaio required"
933
a2947c33
VF
934 @classmethod
935 def io_uring(cls):
936 """Is io_uring available?"""
937 return Requirements._io_uring, "io_uring required"
938
704cc4df
VF
939 @classmethod
940 def zbd(cls):
941 """Is ZBD support available?"""
b1bc705e
VF
942 return Requirements._zbd, "Zoned block device support required"
943
704cc4df
VF
944 @classmethod
945 def root(cls):
946 """Are we running as root?"""
b1bc705e
VF
947 return Requirements._root, "root required"
948
704cc4df
VF
949 @classmethod
950 def zoned_nullb(cls):
951 """Are zoned null block devices available?"""
b1bc705e
VF
952 return Requirements._zoned_nullb, "Zoned null block device support required"
953
704cc4df
VF
954 @classmethod
955 def not_macos(cls):
956 """Are we running on a platform other than macOS?"""
b1bc705e
VF
957 return Requirements._not_macos, "platform other than macOS required"
958
704cc4df
VF
959 @classmethod
960 def not_windows(cls):
961 """Are we running on a platform other than Windws?"""
c58b33b4
VF
962 return Requirements._not_windows, "platform other than Windows required"
963
704cc4df
VF
964 @classmethod
965 def unittests(cls):
966 """Were unittests built?"""
b1bc705e
VF
967 return Requirements._unittests, "Unittests support required"
968
704cc4df
VF
969 @classmethod
970 def cpucount4(cls):
971 """Do we have at least 4 CPUs?"""
b1bc705e
VF
972 return Requirements._cpucount4, "4+ CPUs required"
973
974
df1eaa36 975SUCCESS_DEFAULT = {
704cc4df
VF
976 'zero_return': True,
977 'stderr_empty': True,
978 'timeout': 600,
979 }
df1eaa36 980SUCCESS_NONZERO = {
704cc4df
VF
981 'zero_return': False,
982 'stderr_empty': False,
983 'timeout': 600,
984 }
df1eaa36 985SUCCESS_STDERR = {
704cc4df
VF
986 'zero_return': True,
987 'stderr_empty': False,
988 'timeout': 600,
989 }
df1eaa36 990TEST_LIST = [
704cc4df
VF
991 {
992 'test_id': 1,
993 'test_class': FioJobTest,
994 'job': 't0001-52c58027.fio',
995 'success': SUCCESS_DEFAULT,
996 'pre_job': None,
997 'pre_success': None,
998 'requirements': [],
999 },
1000 {
1001 'test_id': 2,
1002 'test_class': FioJobTest,
1003 'job': 't0002-13af05ae-post.fio',
1004 'success': SUCCESS_DEFAULT,
1005 'pre_job': 't0002-13af05ae-pre.fio',
1006 'pre_success': None,
1007 'requirements': [Requirements.linux, Requirements.libaio],
1008 },
1009 {
1010 'test_id': 3,
1011 'test_class': FioJobTest,
1012 'job': 't0003-0ae2c6e1-post.fio',
1013 'success': SUCCESS_NONZERO,
1014 'pre_job': 't0003-0ae2c6e1-pre.fio',
1015 'pre_success': SUCCESS_DEFAULT,
1016 'requirements': [Requirements.linux, Requirements.libaio],
1017 },
1018 {
1019 'test_id': 4,
1020 'test_class': FioJobTest,
1021 'job': 't0004-8a99fdf6.fio',
1022 'success': SUCCESS_DEFAULT,
1023 'pre_job': None,
1024 'pre_success': None,
1025 'requirements': [Requirements.linux, Requirements.libaio],
1026 },
1027 {
1028 'test_id': 5,
1029 'test_class': FioJobTest_t0005,
1030 'job': 't0005-f7078f7b.fio',
1031 'success': SUCCESS_DEFAULT,
1032 'pre_job': None,
1033 'pre_success': None,
1034 'output_format': 'json',
1035 'requirements': [Requirements.not_windows],
1036 },
1037 {
1038 'test_id': 6,
1039 'test_class': FioJobTest_t0006,
1040 'job': 't0006-82af2a7c.fio',
1041 'success': SUCCESS_DEFAULT,
1042 'pre_job': None,
1043 'pre_success': None,
1044 'output_format': 'json',
1045 'requirements': [Requirements.linux, Requirements.libaio],
1046 },
1047 {
1048 'test_id': 7,
1049 'test_class': FioJobTest_t0007,
1050 'job': 't0007-37cf9e3c.fio',
1051 'success': SUCCESS_DEFAULT,
1052 'pre_job': None,
1053 'pre_success': None,
1054 'output_format': 'json',
1055 'requirements': [],
1056 },
1057 {
1058 'test_id': 8,
1059 'test_class': FioJobTest_t0008,
1060 'job': 't0008-ae2fafc8.fio',
1061 'success': SUCCESS_DEFAULT,
1062 'pre_job': None,
1063 'pre_success': None,
1064 'output_format': 'json',
1065 'requirements': [],
1066 },
1067 {
1068 'test_id': 9,
1069 'test_class': FioJobTest_t0009,
1070 'job': 't0009-f8b0bd10.fio',
1071 'success': SUCCESS_DEFAULT,
1072 'pre_job': None,
1073 'pre_success': None,
1074 'output_format': 'json',
1075 'requirements': [Requirements.not_macos,
1076 Requirements.cpucount4],
1077 # mac os does not support CPU affinity
1078 },
1079 {
1080 'test_id': 10,
1081 'test_class': FioJobTest,
1082 'job': 't0010-b7aae4ba.fio',
1083 'success': SUCCESS_DEFAULT,
1084 'pre_job': None,
1085 'pre_success': None,
1086 'requirements': [],
1087 },
1088 {
1089 'test_id': 11,
0a602473 1090 'test_class': FioJobTest_iops_rate,
704cc4df
VF
1091 'job': 't0011-5d2788d5.fio',
1092 'success': SUCCESS_DEFAULT,
1093 'pre_job': None,
1094 'pre_success': None,
1095 'output_format': 'json',
1096 'requirements': [],
1097 },
0a602473
BVA
1098 {
1099 'test_id': 12,
d4e74fda 1100 'test_class': FioJobTest_t0012,
0a602473
BVA
1101 'job': 't0012.fio',
1102 'success': SUCCESS_DEFAULT,
1103 'pre_job': None,
1104 'pre_success': None,
1105 'output_format': 'json',
d4e74fda 1106 'requirements': [],
0a602473 1107 },
f0c7ae7a
BVA
1108 {
1109 'test_id': 13,
061a0773 1110 'test_class': FioJobTest,
f0c7ae7a
BVA
1111 'job': 't0013.fio',
1112 'success': SUCCESS_DEFAULT,
1113 'pre_job': None,
1114 'pre_success': None,
1115 'output_format': 'json',
1116 'requirements': [],
1117 },
d4e74fda
DB
1118 {
1119 'test_id': 14,
1120 'test_class': FioJobTest_t0014,
1121 'job': 't0014.fio',
1122 'success': SUCCESS_DEFAULT,
1123 'pre_job': None,
1124 'pre_success': None,
1125 'output_format': 'json',
1126 'requirements': [],
1127 },
60ebb939
VF
1128 {
1129 'test_id': 15,
1130 'test_class': FioJobTest_t0015,
1131 'job': 't0015-e78980ff.fio',
1132 'success': SUCCESS_DEFAULT,
1133 'pre_job': None,
1134 'pre_success': None,
1135 'output_format': 'json',
1136 'requirements': [Requirements.linux, Requirements.libaio],
1137 },
de31fe9a
VF
1138 {
1139 'test_id': 16,
1140 'test_class': FioJobTest_t0015,
f045d5f8 1141 'job': 't0016-d54ae22.fio',
de31fe9a
VF
1142 'success': SUCCESS_DEFAULT,
1143 'pre_job': None,
1144 'pre_success': None,
1145 'output_format': 'json',
1146 'requirements': [],
1147 },
31a58cba
VF
1148 {
1149 'test_id': 17,
1150 'test_class': FioJobTest_t0015,
1151 'job': 't0017.fio',
1152 'success': SUCCESS_DEFAULT,
1153 'pre_job': None,
1154 'pre_success': None,
1155 'output_format': 'json',
1156 'requirements': [Requirements.not_windows],
1157 },
a2947c33
VF
1158 {
1159 'test_id': 18,
1160 'test_class': FioJobTest,
1161 'job': 't0018.fio',
1162 'success': SUCCESS_DEFAULT,
1163 'pre_job': None,
1164 'pre_success': None,
1165 'requirements': [Requirements.linux, Requirements.io_uring],
1166 },
ef54f290
VF
1167 {
1168 'test_id': 19,
1169 'test_class': FioJobTest_t0019,
1170 'job': 't0019.fio',
1171 'success': SUCCESS_DEFAULT,
1172 'pre_job': None,
1173 'pre_success': None,
1174 'requirements': [],
1175 },
1176 {
1177 'test_id': 20,
1178 'test_class': FioJobTest_t0020,
1179 'job': 't0020.fio',
1180 'success': SUCCESS_DEFAULT,
1181 'pre_job': None,
1182 'pre_success': None,
1183 'requirements': [],
1184 },
eb40b275
VF
1185 {
1186 'test_id': 21,
1187 'test_class': FioJobTest_t0020,
1188 'job': 't0021.fio',
1189 'success': SUCCESS_DEFAULT,
1190 'pre_job': None,
1191 'pre_success': None,
1192 'requirements': [],
1193 },
1194 {
1195 'test_id': 22,
1196 'test_class': FioJobTest_t0022,
1197 'job': 't0022.fio',
1198 'success': SUCCESS_DEFAULT,
1199 'pre_job': None,
1200 'pre_success': None,
1201 'requirements': [],
1202 },
c37183f8
VF
1203 {
1204 'test_id': 23,
1205 'test_class': FioJobTest_t0023,
1206 'job': 't0023.fio',
1207 'success': SUCCESS_DEFAULT,
1208 'pre_job': None,
1209 'pre_success': None,
1210 'requirements': [],
1211 },
2d5c4600
VF
1212 {
1213 'test_id': 24,
1214 'test_class': FioJobTest_t0024,
1215 'job': 't0024.fio',
1216 'success': SUCCESS_DEFAULT,
1217 'pre_job': None,
1218 'pre_success': None,
1219 'requirements': [],
1220 },
d661fb18
SK
1221 {
1222 'test_id': 25,
1223 'test_class': FioJobTest_t0025,
1224 'job': 't0025.fio',
1225 'success': SUCCESS_DEFAULT,
1226 'pre_job': None,
1227 'pre_success': None,
1228 'output_format': 'json',
1229 'requirements': [],
1230 },
c4704c08
SK
1231 {
1232 'test_id': 26,
1233 'test_class': FioJobTest,
1234 'job': 't0026.fio',
1235 'success': SUCCESS_DEFAULT,
1236 'pre_job': None,
1237 'pre_success': None,
1238 'requirements': [Requirements.not_windows],
1239 },
ede04c27
LG
1240 {
1241 'test_id': 27,
1242 'test_class': FioJobTest_t0027,
1243 'job': 't0027.fio',
1244 'success': SUCCESS_DEFAULT,
1245 'pre_job': None,
1246 'pre_success': None,
1247 'requirements': [],
1248 },
23fb1642
VF
1249 {
1250 'test_id': 28,
1251 'test_class': FioJobTest,
1252 'job': 't0028-c6cade16.fio',
1253 'success': SUCCESS_DEFAULT,
1254 'pre_job': None,
1255 'pre_success': None,
1256 'requirements': [],
1257 },
704cc4df
VF
1258 {
1259 'test_id': 1000,
1260 'test_class': FioExeTest,
1261 'exe': 't/axmap',
1262 'parameters': None,
1263 'success': SUCCESS_DEFAULT,
1264 'requirements': [],
1265 },
1266 {
1267 'test_id': 1001,
1268 'test_class': FioExeTest,
1269 'exe': 't/ieee754',
1270 'parameters': None,
1271 'success': SUCCESS_DEFAULT,
1272 'requirements': [],
1273 },
1274 {
1275 'test_id': 1002,
1276 'test_class': FioExeTest,
1277 'exe': 't/lfsr-test',
1278 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
1279 'success': SUCCESS_STDERR,
1280 'requirements': [],
1281 },
1282 {
1283 'test_id': 1003,
1284 'test_class': FioExeTest,
1285 'exe': 't/readonly.py',
1286 'parameters': ['-f', '{fio_path}'],
1287 'success': SUCCESS_DEFAULT,
1288 'requirements': [],
1289 },
1290 {
1291 'test_id': 1004,
1292 'test_class': FioExeTest,
1293 'exe': 't/steadystate_tests.py',
1294 'parameters': ['{fio_path}'],
1295 'success': SUCCESS_DEFAULT,
1296 'requirements': [],
1297 },
1298 {
1299 'test_id': 1005,
1300 'test_class': FioExeTest,
1301 'exe': 't/stest',
1302 'parameters': None,
1303 'success': SUCCESS_STDERR,
1304 'requirements': [],
1305 },
1306 {
1307 'test_id': 1006,
1308 'test_class': FioExeTest,
1309 'exe': 't/strided.py',
1310 'parameters': ['{fio_path}'],
1311 'success': SUCCESS_DEFAULT,
1312 'requirements': [],
1313 },
1314 {
1315 'test_id': 1007,
1316 'test_class': FioExeTest,
037b2b50
DF
1317 'exe': 't/zbd/run-tests-against-nullb',
1318 'parameters': ['-s', '1'],
704cc4df
VF
1319 'success': SUCCESS_DEFAULT,
1320 'requirements': [Requirements.linux, Requirements.zbd,
1321 Requirements.root],
1322 },
1323 {
1324 'test_id': 1008,
1325 'test_class': FioExeTest,
037b2b50
DF
1326 'exe': 't/zbd/run-tests-against-nullb',
1327 'parameters': ['-s', '2'],
704cc4df
VF
1328 'success': SUCCESS_DEFAULT,
1329 'requirements': [Requirements.linux, Requirements.zbd,
1330 Requirements.root, Requirements.zoned_nullb],
1331 },
1332 {
1333 'test_id': 1009,
1334 'test_class': FioExeTest,
1335 'exe': 'unittests/unittest',
1336 'parameters': None,
1337 'success': SUCCESS_DEFAULT,
1338 'requirements': [Requirements.unittests],
1339 },
1340 {
1341 'test_id': 1010,
1342 'test_class': FioExeTest,
1343 'exe': 't/latency_percentiles.py',
1344 'parameters': ['-f', '{fio_path}'],
1345 'success': SUCCESS_DEFAULT,
1346 'requirements': [],
1347 },
8403eca6
VF
1348 {
1349 'test_id': 1011,
1350 'test_class': FioExeTest,
1351 'exe': 't/jsonplus2csv_test.py',
1352 'parameters': ['-f', '{fio_path}'],
1353 'success': SUCCESS_DEFAULT,
1354 'requirements': [],
1355 },
03900b0b 1356 {
1357 'test_id': 1012,
1358 'test_class': FioExeTest,
1359 'exe': 't/log_compression.py',
1360 'parameters': ['-f', '{fio_path}'],
1361 'success': SUCCESS_DEFAULT,
1362 'requirements': [],
1363 },
df1eaa36
VF
1364]
1365
1366
1367def parse_args():
704cc4df
VF
1368 """Parse command-line arguments."""
1369
df1eaa36
VF
1370 parser = argparse.ArgumentParser()
1371 parser.add_argument('-r', '--fio-root',
1372 help='fio root path')
1373 parser.add_argument('-f', '--fio',
1374 help='path to fio executable (e.g., ./fio)')
1375 parser.add_argument('-a', '--artifact-root',
1376 help='artifact root directory')
1377 parser.add_argument('-s', '--skip', nargs='+', type=int,
1378 help='list of test(s) to skip')
1379 parser.add_argument('-o', '--run-only', nargs='+', type=int,
1380 help='list of test(s) to run, skipping all others')
6d5470c3
VF
1381 parser.add_argument('-d', '--debug', action='store_true',
1382 help='provide debug output')
b1bc705e
VF
1383 parser.add_argument('-k', '--skip-req', action='store_true',
1384 help='skip requirements checking')
58a77d2a
VF
1385 parser.add_argument('-p', '--pass-through', action='append',
1386 help='pass-through an argument to an executable test')
df1eaa36
VF
1387 args = parser.parse_args()
1388
1389 return args
1390
1391
1392def main():
704cc4df
VF
1393 """Entry point."""
1394
df1eaa36 1395 args = parse_args()
6d5470c3
VF
1396 if args.debug:
1397 logging.basicConfig(level=logging.DEBUG)
1398 else:
1399 logging.basicConfig(level=logging.INFO)
1400
58a77d2a
VF
1401 pass_through = {}
1402 if args.pass_through:
1403 for arg in args.pass_through:
1404 if not ':' in arg:
1405 print("Invalid --pass-through argument '%s'" % arg)
1406 print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
1407 return
061a0773 1408 split = arg.split(":", 1)
58a77d2a 1409 pass_through[int(split[0])] = split[1]
061a0773 1410 logging.debug("Pass-through arguments: %s", pass_through)
58a77d2a 1411
df1eaa36
VF
1412 if args.fio_root:
1413 fio_root = args.fio_root
1414 else:
6d5470c3
VF
1415 fio_root = str(Path(__file__).absolute().parent.parent)
1416 print("fio root is %s" % fio_root)
df1eaa36
VF
1417
1418 if args.fio:
1419 fio_path = args.fio
1420 else:
742b8799
VF
1421 if platform.system() == "Windows":
1422 fio_exe = "fio.exe"
1423 else:
1424 fio_exe = "fio"
1425 fio_path = os.path.join(fio_root, fio_exe)
6d5470c3
VF
1426 print("fio path is %s" % fio_path)
1427 if not shutil.which(fio_path):
1428 print("Warning: fio executable not found")
df1eaa36
VF
1429
1430 artifact_root = args.artifact_root if args.artifact_root else \
1431 "fio-test-{0}".format(time.strftime("%Y%m%d-%H%M%S"))
1432 os.mkdir(artifact_root)
1433 print("Artifact directory is %s" % artifact_root)
1434
b1bc705e
VF
1435 if not args.skip_req:
1436 req = Requirements(fio_root)
1437
df1eaa36
VF
1438 passed = 0
1439 failed = 0
1440 skipped = 0
1441
1442 for config in TEST_LIST:
1443 if (args.skip and config['test_id'] in args.skip) or \
1444 (args.run_only and config['test_id'] not in args.run_only):
1445 skipped = skipped + 1
b1bc705e 1446 print("Test {0} SKIPPED (User request)".format(config['test_id']))
df1eaa36
VF
1447 continue
1448
1449 if issubclass(config['test_class'], FioJobTest):
1450 if config['pre_job']:
1451 fio_pre_job = os.path.join(fio_root, 't', 'jobs',
1452 config['pre_job'])
1453 else:
1454 fio_pre_job = None
1455 if config['pre_success']:
1456 fio_pre_success = config['pre_success']
1457 else:
1458 fio_pre_success = None
1459 if 'output_format' in config:
1460 output_format = config['output_format']
1461 else:
1462 output_format = 'normal'
1463 test = config['test_class'](
1464 fio_path,
1465 os.path.join(fio_root, 't', 'jobs', config['job']),
1466 config['success'],
1467 fio_pre_job=fio_pre_job,
1468 fio_pre_success=fio_pre_success,
1469 output_format=output_format)
9393cdaa 1470 desc = config['job']
df1eaa36
VF
1471 elif issubclass(config['test_class'], FioExeTest):
1472 exe_path = os.path.join(fio_root, config['exe'])
1473 if config['parameters']:
1474 parameters = [p.format(fio_path=fio_path) for p in config['parameters']]
1475 else:
58a77d2a 1476 parameters = []
742b8799 1477 if Path(exe_path).suffix == '.py' and platform.system() == "Windows":
58a77d2a 1478 parameters.insert(0, exe_path)
742b8799 1479 exe_path = "python.exe"
58a77d2a
VF
1480 if config['test_id'] in pass_through:
1481 parameters += pass_through[config['test_id']].split()
df1eaa36
VF
1482 test = config['test_class'](exe_path, parameters,
1483 config['success'])
9393cdaa 1484 desc = config['exe']
df1eaa36
VF
1485 else:
1486 print("Test {0} FAILED: unable to process test config".format(config['test_id']))
1487 failed = failed + 1
1488 continue
1489
b1bc705e 1490 if not args.skip_req:
704cc4df 1491 reqs_met = True
b1bc705e 1492 for req in config['requirements']:
704cc4df
VF
1493 reqs_met, reason = req()
1494 logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason,
1495 reqs_met)
1496 if not reqs_met:
b1bc705e 1497 break
704cc4df 1498 if not reqs_met:
9393cdaa 1499 print("Test {0} SKIPPED ({1}) {2}".format(config['test_id'], reason, desc))
b1bc705e
VF
1500 skipped = skipped + 1
1501 continue
1502
aa9f2627
VF
1503 try:
1504 test.setup(artifact_root, config['test_id'])
1505 test.run()
1506 test.check_result()
1507 except KeyboardInterrupt:
1508 break
1509 except Exception as e:
1510 test.passed = False
1511 test.failure_reason += str(e)
1512 logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc())
df1eaa36
VF
1513 if test.passed:
1514 result = "PASSED"
1515 passed = passed + 1
1516 else:
1517 result = "FAILED: {0}".format(test.failure_reason)
1518 failed = failed + 1
15a73987
VF
1519 contents, _ = FioJobTest.get_file(test.stderr_file)
1520 logging.debug("Test %d: stderr:\n%s", config['test_id'], contents)
1521 contents, _ = FioJobTest.get_file(test.stdout_file)
1522 logging.debug("Test %d: stdout:\n%s", config['test_id'], contents)
9393cdaa 1523 print("Test {0} {1} {2}".format(config['test_id'], result, desc))
df1eaa36
VF
1524
1525 print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))
1526
1527 sys.exit(failed)
1528
1529
1530if __name__ == '__main__':
1531 main()