test: add large pattern test
[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
390 write['io_kbytes'] ~ 16568
391
392 I did runs with fio-ae2fafc8 and saw write['io_kbytes'] values of
393 16585, 16588. With two runs of fio-3.16 I obtained 16568"""
394
395 def check_result(self):
396 super(FioJobTest_t0008, self).check_result()
397
398 if not self.passed:
399 return
400
401 ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568
704cc4df 402 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36
VF
403
404 if ratio < 0.99 or ratio > 1.01:
405 self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
406 self.passed = False
407 if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768:
408 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
409 self.passed = False
410
411
412class FioJobTest_t0009(FioJobTest):
413 """Test consists of fio test job t0009
414 Confirm that runtime >= 60s"""
415
416 def check_result(self):
417 super(FioJobTest_t0009, self).check_result()
418
419 if not self.passed:
420 return
421
704cc4df 422 logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
df1eaa36
VF
423
424 if self.json_data['jobs'][0]['elapsed'] < 60:
425 self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason)
426 self.passed = False
427
428
d4e74fda
DB
429class FioJobTest_t0012(FioJobTest):
430 """Test consists of fio test job t0012
431 Confirm ratios of job iops are 1:5:10
432 job1,job2,job3 respectively"""
433
434 def check_result(self):
435 super(FioJobTest_t0012, self).check_result()
436
437 if not self.passed:
438 return
439
440 iops_files = []
114eadb2
VF
441 for i in range(1, 4):
442 filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(
443 self.fio_job), i))
0f5234b4
VF
444 file_data = self.get_file_fail(filename)
445 if not file_data:
d4e74fda
DB
446 return
447
448 iops_files.append(file_data.splitlines())
449
450 # there are 9 samples for job1 and job2, 4 samples for job3
451 iops1 = 0.0
452 iops2 = 0.0
453 iops3 = 0.0
454 for i in range(9):
455 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
456 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
457 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
458
459 ratio1 = iops3/iops2
460 ratio2 = iops3/iops1
114eadb2
VF
461 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
462 "job3/job2={4:.3f} job3/job1={5:.3f}".format(i, iops1, iops2, iops3, ratio1,
463 ratio2))
d4e74fda
DB
464
465 # test job1 and job2 succeeded to recalibrate
466 if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13:
114eadb2
VF
467 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
468 "expected r1~2 r2~10 got r1={3:.3f} r2={4:.3f},".format(iops1, iops2, iops3,
469 ratio1, ratio2)
d4e74fda
DB
470 self.passed = False
471 return
472
473
474class FioJobTest_t0014(FioJobTest):
475 """Test consists of fio test job t0014
476 Confirm that job1_iops / job2_iops ~ 1:2 for entire duration
477 and that job1_iops / job3_iops ~ 1:3 for first half of duration.
478
479 The test is about making sure the flow feature can
480 re-calibrate the activity dynamically"""
481
482 def check_result(self):
483 super(FioJobTest_t0014, self).check_result()
484
485 if not self.passed:
486 return
487
488 iops_files = []
114eadb2
VF
489 for i in range(1, 4):
490 filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(
491 self.fio_job), i))
0f5234b4
VF
492 file_data = self.get_file_fail(filename)
493 if not file_data:
d4e74fda
DB
494 return
495
496 iops_files.append(file_data.splitlines())
497
498 # there are 9 samples for job1 and job2, 4 samples for job3
499 iops1 = 0.0
500 iops2 = 0.0
501 iops3 = 0.0
502 for i in range(9):
503 if i < 4:
504 iops3 = iops3 + float(iops_files[2][i].split(',')[1])
505 elif i == 4:
506 ratio1 = iops1 / iops2
507 ratio2 = iops1 / iops3
508
509
510 if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45:
114eadb2
VF
511 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \
512 "expected r1~0.5 r2~0.33 got r1={3:.3f} r2={4:.3f},".format(
513 iops1, iops2, iops3, ratio1, ratio2)
d4e74fda
DB
514 self.passed = False
515
516 iops1 = iops1 + float(iops_files[0][i].split(',')[1])
517 iops2 = iops2 + float(iops_files[1][i].split(',')[1])
518
519 ratio1 = iops1/iops2
520 ratio2 = iops1/iops3
114eadb2
VF
521 logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \
522 "job1/job2={4:.3f} job1/job3={5:.3f}".format(i, iops1, iops2, iops3,
523 ratio1, ratio2))
d4e74fda
DB
524
525 # test job1 and job2 succeeded to recalibrate
526 if ratio1 < 0.43 or ratio1 > 0.57:
114eadb2
VF
527 self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} expected ratio~0.5 " \
528 "got ratio={2:.3f},".format(iops1, iops2, ratio1)
d4e74fda
DB
529 self.passed = False
530 return
531
532
60ebb939 533class FioJobTest_t0015(FioJobTest):
de31fe9a 534 """Test consists of fio test jobs t0015 and t0016
60ebb939
VF
535 Confirm that mean(slat) + mean(clat) = mean(tlat)"""
536
537 def check_result(self):
538 super(FioJobTest_t0015, self).check_result()
539
540 if not self.passed:
541 return
542
543 slat = self.json_data['jobs'][0]['read']['slat_ns']['mean']
544 clat = self.json_data['jobs'][0]['read']['clat_ns']['mean']
545 tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean']
546 logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat)
547
548 if abs(slat + clat - tlat) > 1:
549 self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format(
550 self.failure_reason, slat, clat, slat+clat, tlat)
551 self.passed = False
552
553
ef54f290
VF
554class FioJobTest_t0019(FioJobTest):
555 """Test consists of fio test job t0019
556 Confirm that all offsets were touched sequentially"""
557
558 def check_result(self):
559 super(FioJobTest_t0019, self).check_result()
560
561 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
562 file_data = self.get_file_fail(bw_log_filename)
563 if not file_data:
114eadb2
VF
564 return
565
ef54f290
VF
566 log_lines = file_data.split('\n')
567
568 prev = -4096
569 for line in log_lines:
570 if len(line.strip()) == 0:
571 continue
572 cur = int(line.split(',')[4])
573 if cur - prev != 4096:
574 self.passed = False
575 self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur)
576 return
577 prev = cur
578
579 if cur/4096 != 255:
580 self.passed = False
581 self.failure_reason = "unexpected last offset {0}".format(cur)
582
583
584class FioJobTest_t0020(FioJobTest):
eb40b275 585 """Test consists of fio test jobs t0020 and t0021
ef54f290
VF
586 Confirm that almost all offsets were touched non-sequentially"""
587
588 def check_result(self):
589 super(FioJobTest_t0020, self).check_result()
590
591 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
592 file_data = self.get_file_fail(bw_log_filename)
593 if not file_data:
114eadb2
VF
594 return
595
ef54f290
VF
596 log_lines = file_data.split('\n')
597
598 seq_count = 0
599 offsets = set()
600
601 prev = int(log_lines[0].split(',')[4])
602 for line in log_lines[1:]:
603 offsets.add(prev/4096)
604 if len(line.strip()) == 0:
605 continue
606 cur = int(line.split(',')[4])
607 if cur - prev == 4096:
608 seq_count += 1
609 prev = cur
610
611 # 10 is an arbitrary threshold
612 if seq_count > 10:
613 self.passed = False
614 self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count)
615
616 if len(offsets) != 256:
617 self.passed = False
618 self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets))
619
620 for i in range(256):
621 if not i in offsets:
622 self.passed = False
623 self.failure_reason += " missing offset {0}".format(i*4096)
624
625
eb40b275
VF
626class FioJobTest_t0022(FioJobTest):
627 """Test consists of fio test job t0022"""
628
629 def check_result(self):
630 super(FioJobTest_t0022, self).check_result()
631
632 bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
0f5234b4
VF
633 file_data = self.get_file_fail(bw_log_filename)
634 if not file_data:
114eadb2
VF
635 return
636
eb40b275
VF
637 log_lines = file_data.split('\n')
638
639 filesize = 1024*1024
640 bs = 4096
641 seq_count = 0
642 offsets = set()
643
644 prev = int(log_lines[0].split(',')[4])
645 for line in log_lines[1:]:
646 offsets.add(prev/bs)
647 if len(line.strip()) == 0:
648 continue
649 cur = int(line.split(',')[4])
650 if cur - prev == bs:
651 seq_count += 1
652 prev = cur
653
654 # 10 is an arbitrary threshold
655 if seq_count > 10:
656 self.passed = False
657 self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count)
658
659 if len(offsets) == filesize/bs:
660 self.passed = False
114eadb2 661 self.failure_reason += " no duplicate offsets found with norandommap=1"
eb40b275
VF
662
663
c37183f8 664class FioJobTest_t0023(FioJobTest):
21a20229 665 """Test consists of fio test job t0023 randtrimwrite test."""
c37183f8 666
1fb78294
VF
667 def check_trimwrite(self, filename):
668 """Make sure that trims are followed by writes of the same size at the same offset."""
669
c37183f8 670 bw_log_filename = os.path.join(self.test_dir, filename)
0f5234b4
VF
671 file_data = self.get_file_fail(bw_log_filename)
672 if not file_data:
114eadb2
VF
673 return
674
c37183f8
VF
675 log_lines = file_data.split('\n')
676
677 prev_ddir = 1
678 for line in log_lines:
679 if len(line.strip()) == 0:
680 continue
681 vals = line.split(',')
682 ddir = int(vals[2])
683 bs = int(vals[3])
684 offset = int(vals[4])
685 if prev_ddir == 1:
686 if ddir != 2:
687 self.passed = False
21a20229
VF
688 self.failure_reason += " {0}: write not preceeded by trim: {1}".format(
689 bw_log_filename, line)
c37183f8
VF
690 break
691 else:
692 if ddir != 1:
693 self.passed = False
21a20229
VF
694 self.failure_reason += " {0}: trim not preceeded by write: {1}".format(
695 bw_log_filename, line)
c37183f8
VF
696 break
697 else:
698 if prev_bs != bs:
699 self.passed = False
21a20229
VF
700 self.failure_reason += " {0}: block size does not match: {1}".format(
701 bw_log_filename, line)
c37183f8
VF
702 break
703 if prev_offset != offset:
704 self.passed = False
21a20229
VF
705 self.failure_reason += " {0}: offset does not match: {1}".format(
706 bw_log_filename, line)
c37183f8
VF
707 break
708 prev_ddir = ddir
709 prev_bs = bs
710 prev_offset = offset
711
712
9e83aa16 713 def check_all_offsets(self, filename, sectorsize, filesize):
21a20229
VF
714 """Make sure all offsets were touched."""
715
0f5234b4
VF
716 file_data = self.get_file_fail(os.path.join(self.test_dir, filename))
717 if not file_data:
9e83aa16
VF
718 return
719
720 log_lines = file_data.split('\n')
721
722 offsets = set()
723
724 for line in log_lines:
725 if len(line.strip()) == 0:
726 continue
727 vals = line.split(',')
728 bs = int(vals[3])
729 offset = int(vals[4])
730 if offset % sectorsize != 0:
731 self.passed = False
21a20229
VF
732 self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(
733 filename, offset, sectorsize)
734 break
9e83aa16
VF
735 if bs % sectorsize != 0:
736 self.passed = False
21a20229
VF
737 self.failure_reason += " {0}: block size {1} not a multiple of sector size " \
738 "{2}".format(filename, bs, sectorsize)
739 break
9e83aa16
VF
740 for i in range(int(bs/sectorsize)):
741 offsets.add(offset/sectorsize + i)
742
743 if len(offsets) != filesize/sectorsize:
744 self.passed = False
21a20229
VF
745 self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(
746 filename, len(offsets), filesize/sectorsize)
9e83aa16 747 else:
21a20229 748 logging.debug("%s: %d sectors touched", filename, len(offsets))
9e83aa16
VF
749
750
c37183f8
VF
751 def check_result(self):
752 super(FioJobTest_t0023, self).check_result()
753
9e83aa16
VF
754 filesize = 1024*1024
755
1fb78294
VF
756 self.check_trimwrite("basic_bw.log")
757 self.check_trimwrite("bs_bw.log")
758 self.check_trimwrite("bsrange_bw.log")
759 self.check_trimwrite("bssplit_bw.log")
760 self.check_trimwrite("basic_no_rm_bw.log")
761 self.check_trimwrite("bs_no_rm_bw.log")
762 self.check_trimwrite("bsrange_no_rm_bw.log")
763 self.check_trimwrite("bssplit_no_rm_bw.log")
c37183f8 764
9e83aa16
VF
765 self.check_all_offsets("basic_bw.log", 4096, filesize)
766 self.check_all_offsets("bs_bw.log", 8192, filesize)
767 self.check_all_offsets("bsrange_bw.log", 512, filesize)
768 self.check_all_offsets("bssplit_bw.log", 512, filesize)
c37183f8
VF
769
770
2d5c4600
VF
771class FioJobTest_t0024(FioJobTest_t0023):
772 """Test consists of fio test job t0024 trimwrite test."""
773
774 def check_result(self):
775 # call FioJobTest_t0023's parent to skip checks done by t0023
776 super(FioJobTest_t0023, self).check_result()
777
778 filesize = 1024*1024
779
780 self.check_trimwrite("basic_bw.log")
781 self.check_trimwrite("bs_bw.log")
782 self.check_trimwrite("bsrange_bw.log")
783 self.check_trimwrite("bssplit_bw.log")
784
785 self.check_all_offsets("basic_bw.log", 4096, filesize)
786 self.check_all_offsets("bs_bw.log", 8192, filesize)
787 self.check_all_offsets("bsrange_bw.log", 512, filesize)
788 self.check_all_offsets("bssplit_bw.log", 512, filesize)
789
790
d661fb18
SK
791class FioJobTest_t0025(FioJobTest):
792 """Test experimental verify read backs written data pattern."""
793 def check_result(self):
794 super(FioJobTest_t0025, self).check_result()
795
796 if not self.passed:
797 return
798
799 if self.json_data['jobs'][0]['read']['io_kbytes'] != 128:
800 self.passed = False
801
ede04c27
LG
802class FioJobTest_t0027(FioJobTest):
803 def setup(self, *args, **kws):
804 super(FioJobTest_t0027, self).setup(*args, **kws)
805 self.pattern_file = os.path.join(self.test_dir, "t0027.pattern")
806 self.output_file = os.path.join(self.test_dir, "t0027file")
807 self.pattern = os.urandom(16 << 10)
808 with open(self.pattern_file, "wb") as f:
809 f.write(self.pattern)
810
811 def check_result(self):
812 super(FioJobTest_t0027, self).check_result()
813
814 if not self.passed:
815 return
816
817 with open(self.output_file, "rb") as f:
818 data = f.read()
819
820 if data != self.pattern:
821 self.passed = False
d661fb18 822
0a602473 823class FioJobTest_iops_rate(FioJobTest):
df1eaa36
VF
824 """Test consists of fio test job t0009
825 Confirm that job0 iops == 1000
826 and that job1_iops / job0_iops ~ 8
827 With two runs of fio-3.16 I observed a ratio of 8.3"""
828
829 def check_result(self):
0a602473 830 super(FioJobTest_iops_rate, self).check_result()
df1eaa36
VF
831
832 if not self.passed:
833 return
834
835 iops1 = self.json_data['jobs'][0]['read']['iops']
7ddc4ed1 836 logging.debug("Test %d: iops1: %f", self.testnum, iops1)
df1eaa36 837 iops2 = self.json_data['jobs'][1]['read']['iops']
7ddc4ed1 838 logging.debug("Test %d: iops2: %f", self.testnum, iops2)
df1eaa36 839 ratio = iops2 / iops1
704cc4df 840 logging.debug("Test %d: ratio: %f", self.testnum, ratio)
df1eaa36 841
0a9b8988 842 if iops1 < 950 or iops1 > 1050:
df1eaa36
VF
843 self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason)
844 self.passed = False
845
d4e74fda 846 if ratio < 6 or ratio > 10:
df1eaa36
VF
847 self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason)
848 self.passed = False
849
850
114eadb2 851class Requirements():
b1bc705e
VF
852 """Requirements consists of multiple run environment characteristics.
853 These are to determine if a particular test can be run"""
854
855 _linux = False
856 _libaio = False
a2947c33 857 _io_uring = False
b1bc705e
VF
858 _zbd = False
859 _root = False
860 _zoned_nullb = False
861 _not_macos = False
c58b33b4 862 _not_windows = False
b1bc705e
VF
863 _unittests = False
864 _cpucount4 = False
865
866 def __init__(self, fio_root):
867 Requirements._not_macos = platform.system() != "Darwin"
c58b33b4 868 Requirements._not_windows = platform.system() != "Windows"
b1bc705e
VF
869 Requirements._linux = platform.system() == "Linux"
870
871 if Requirements._linux:
15a73987
VF
872 config_file = os.path.join(fio_root, "config-host.h")
873 contents, success = FioJobTest.get_file(config_file)
874 if not success:
b1bc705e
VF
875 print("Unable to open {0} to check requirements".format(config_file))
876 Requirements._zbd = True
877 else:
b7694961 878 Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents
b1bc705e
VF
879 Requirements._libaio = "CONFIG_LIBAIO" in contents
880
a2947c33
VF
881 contents, success = FioJobTest.get_file("/proc/kallsyms")
882 if not success:
883 print("Unable to open '/proc/kallsyms' to probe for io_uring support")
884 else:
885 Requirements._io_uring = "io_uring_setup" in contents
886
b1bc705e
VF
887 Requirements._root = (os.geteuid() == 0)
888 if Requirements._zbd and Requirements._root:
8854e368
VF
889 try:
890 subprocess.run(["modprobe", "null_blk"],
891 stdout=subprocess.PIPE,
892 stderr=subprocess.PIPE)
893 if os.path.exists("/sys/module/null_blk/parameters/zoned"):
894 Requirements._zoned_nullb = True
895 except Exception:
896 pass
b1bc705e 897
742b8799
VF
898 if platform.system() == "Windows":
899 utest_exe = "unittest.exe"
900 else:
901 utest_exe = "unittest"
902 unittest_path = os.path.join(fio_root, "unittests", utest_exe)
b1bc705e
VF
903 Requirements._unittests = os.path.exists(unittest_path)
904
905 Requirements._cpucount4 = multiprocessing.cpu_count() >= 4
906
907 req_list = [Requirements.linux,
908 Requirements.libaio,
a2947c33 909 Requirements.io_uring,
b1bc705e
VF
910 Requirements.zbd,
911 Requirements.root,
912 Requirements.zoned_nullb,
913 Requirements.not_macos,
c58b33b4 914 Requirements.not_windows,
b1bc705e
VF
915 Requirements.unittests,
916 Requirements.cpucount4]
917 for req in req_list:
918 value, desc = req()
704cc4df 919 logging.debug("Requirements: Requirement '%s' met? %s", desc, value)
b1bc705e 920
704cc4df
VF
921 @classmethod
922 def linux(cls):
923 """Are we running on Linux?"""
b1bc705e
VF
924 return Requirements._linux, "Linux required"
925
704cc4df
VF
926 @classmethod
927 def libaio(cls):
928 """Is libaio available?"""
b1bc705e
VF
929 return Requirements._libaio, "libaio required"
930
a2947c33
VF
931 @classmethod
932 def io_uring(cls):
933 """Is io_uring available?"""
934 return Requirements._io_uring, "io_uring required"
935
704cc4df
VF
936 @classmethod
937 def zbd(cls):
938 """Is ZBD support available?"""
b1bc705e
VF
939 return Requirements._zbd, "Zoned block device support required"
940
704cc4df
VF
941 @classmethod
942 def root(cls):
943 """Are we running as root?"""
b1bc705e
VF
944 return Requirements._root, "root required"
945
704cc4df
VF
946 @classmethod
947 def zoned_nullb(cls):
948 """Are zoned null block devices available?"""
b1bc705e
VF
949 return Requirements._zoned_nullb, "Zoned null block device support required"
950
704cc4df
VF
951 @classmethod
952 def not_macos(cls):
953 """Are we running on a platform other than macOS?"""
b1bc705e
VF
954 return Requirements._not_macos, "platform other than macOS required"
955
704cc4df
VF
956 @classmethod
957 def not_windows(cls):
958 """Are we running on a platform other than Windws?"""
c58b33b4
VF
959 return Requirements._not_windows, "platform other than Windows required"
960
704cc4df
VF
961 @classmethod
962 def unittests(cls):
963 """Were unittests built?"""
b1bc705e
VF
964 return Requirements._unittests, "Unittests support required"
965
704cc4df
VF
966 @classmethod
967 def cpucount4(cls):
968 """Do we have at least 4 CPUs?"""
b1bc705e
VF
969 return Requirements._cpucount4, "4+ CPUs required"
970
971
df1eaa36 972SUCCESS_DEFAULT = {
704cc4df
VF
973 'zero_return': True,
974 'stderr_empty': True,
975 'timeout': 600,
976 }
df1eaa36 977SUCCESS_NONZERO = {
704cc4df
VF
978 'zero_return': False,
979 'stderr_empty': False,
980 'timeout': 600,
981 }
df1eaa36 982SUCCESS_STDERR = {
704cc4df
VF
983 'zero_return': True,
984 'stderr_empty': False,
985 'timeout': 600,
986 }
df1eaa36 987TEST_LIST = [
704cc4df
VF
988 {
989 'test_id': 1,
990 'test_class': FioJobTest,
991 'job': 't0001-52c58027.fio',
992 'success': SUCCESS_DEFAULT,
993 'pre_job': None,
994 'pre_success': None,
995 'requirements': [],
996 },
997 {
998 'test_id': 2,
999 'test_class': FioJobTest,
1000 'job': 't0002-13af05ae-post.fio',
1001 'success': SUCCESS_DEFAULT,
1002 'pre_job': 't0002-13af05ae-pre.fio',
1003 'pre_success': None,
1004 'requirements': [Requirements.linux, Requirements.libaio],
1005 },
1006 {
1007 'test_id': 3,
1008 'test_class': FioJobTest,
1009 'job': 't0003-0ae2c6e1-post.fio',
1010 'success': SUCCESS_NONZERO,
1011 'pre_job': 't0003-0ae2c6e1-pre.fio',
1012 'pre_success': SUCCESS_DEFAULT,
1013 'requirements': [Requirements.linux, Requirements.libaio],
1014 },
1015 {
1016 'test_id': 4,
1017 'test_class': FioJobTest,
1018 'job': 't0004-8a99fdf6.fio',
1019 'success': SUCCESS_DEFAULT,
1020 'pre_job': None,
1021 'pre_success': None,
1022 'requirements': [Requirements.linux, Requirements.libaio],
1023 },
1024 {
1025 'test_id': 5,
1026 'test_class': FioJobTest_t0005,
1027 'job': 't0005-f7078f7b.fio',
1028 'success': SUCCESS_DEFAULT,
1029 'pre_job': None,
1030 'pre_success': None,
1031 'output_format': 'json',
1032 'requirements': [Requirements.not_windows],
1033 },
1034 {
1035 'test_id': 6,
1036 'test_class': FioJobTest_t0006,
1037 'job': 't0006-82af2a7c.fio',
1038 'success': SUCCESS_DEFAULT,
1039 'pre_job': None,
1040 'pre_success': None,
1041 'output_format': 'json',
1042 'requirements': [Requirements.linux, Requirements.libaio],
1043 },
1044 {
1045 'test_id': 7,
1046 'test_class': FioJobTest_t0007,
1047 'job': 't0007-37cf9e3c.fio',
1048 'success': SUCCESS_DEFAULT,
1049 'pre_job': None,
1050 'pre_success': None,
1051 'output_format': 'json',
1052 'requirements': [],
1053 },
1054 {
1055 'test_id': 8,
1056 'test_class': FioJobTest_t0008,
1057 'job': 't0008-ae2fafc8.fio',
1058 'success': SUCCESS_DEFAULT,
1059 'pre_job': None,
1060 'pre_success': None,
1061 'output_format': 'json',
1062 'requirements': [],
1063 },
1064 {
1065 'test_id': 9,
1066 'test_class': FioJobTest_t0009,
1067 'job': 't0009-f8b0bd10.fio',
1068 'success': SUCCESS_DEFAULT,
1069 'pre_job': None,
1070 'pre_success': None,
1071 'output_format': 'json',
1072 'requirements': [Requirements.not_macos,
1073 Requirements.cpucount4],
1074 # mac os does not support CPU affinity
1075 },
1076 {
1077 'test_id': 10,
1078 'test_class': FioJobTest,
1079 'job': 't0010-b7aae4ba.fio',
1080 'success': SUCCESS_DEFAULT,
1081 'pre_job': None,
1082 'pre_success': None,
1083 'requirements': [],
1084 },
1085 {
1086 'test_id': 11,
0a602473 1087 'test_class': FioJobTest_iops_rate,
704cc4df
VF
1088 'job': 't0011-5d2788d5.fio',
1089 'success': SUCCESS_DEFAULT,
1090 'pre_job': None,
1091 'pre_success': None,
1092 'output_format': 'json',
1093 'requirements': [],
1094 },
0a602473
BVA
1095 {
1096 'test_id': 12,
d4e74fda 1097 'test_class': FioJobTest_t0012,
0a602473
BVA
1098 'job': 't0012.fio',
1099 'success': SUCCESS_DEFAULT,
1100 'pre_job': None,
1101 'pre_success': None,
1102 'output_format': 'json',
d4e74fda 1103 'requirements': [],
0a602473 1104 },
f0c7ae7a
BVA
1105 {
1106 'test_id': 13,
061a0773 1107 'test_class': FioJobTest,
f0c7ae7a
BVA
1108 'job': 't0013.fio',
1109 'success': SUCCESS_DEFAULT,
1110 'pre_job': None,
1111 'pre_success': None,
1112 'output_format': 'json',
1113 'requirements': [],
1114 },
d4e74fda
DB
1115 {
1116 'test_id': 14,
1117 'test_class': FioJobTest_t0014,
1118 'job': 't0014.fio',
1119 'success': SUCCESS_DEFAULT,
1120 'pre_job': None,
1121 'pre_success': None,
1122 'output_format': 'json',
1123 'requirements': [],
1124 },
60ebb939
VF
1125 {
1126 'test_id': 15,
1127 'test_class': FioJobTest_t0015,
1128 'job': 't0015-e78980ff.fio',
1129 'success': SUCCESS_DEFAULT,
1130 'pre_job': None,
1131 'pre_success': None,
1132 'output_format': 'json',
1133 'requirements': [Requirements.linux, Requirements.libaio],
1134 },
de31fe9a
VF
1135 {
1136 'test_id': 16,
1137 'test_class': FioJobTest_t0015,
f045d5f8 1138 'job': 't0016-d54ae22.fio',
de31fe9a
VF
1139 'success': SUCCESS_DEFAULT,
1140 'pre_job': None,
1141 'pre_success': None,
1142 'output_format': 'json',
1143 'requirements': [],
1144 },
31a58cba
VF
1145 {
1146 'test_id': 17,
1147 'test_class': FioJobTest_t0015,
1148 'job': 't0017.fio',
1149 'success': SUCCESS_DEFAULT,
1150 'pre_job': None,
1151 'pre_success': None,
1152 'output_format': 'json',
1153 'requirements': [Requirements.not_windows],
1154 },
a2947c33
VF
1155 {
1156 'test_id': 18,
1157 'test_class': FioJobTest,
1158 'job': 't0018.fio',
1159 'success': SUCCESS_DEFAULT,
1160 'pre_job': None,
1161 'pre_success': None,
1162 'requirements': [Requirements.linux, Requirements.io_uring],
1163 },
ef54f290
VF
1164 {
1165 'test_id': 19,
1166 'test_class': FioJobTest_t0019,
1167 'job': 't0019.fio',
1168 'success': SUCCESS_DEFAULT,
1169 'pre_job': None,
1170 'pre_success': None,
1171 'requirements': [],
1172 },
1173 {
1174 'test_id': 20,
1175 'test_class': FioJobTest_t0020,
1176 'job': 't0020.fio',
1177 'success': SUCCESS_DEFAULT,
1178 'pre_job': None,
1179 'pre_success': None,
1180 'requirements': [],
1181 },
eb40b275
VF
1182 {
1183 'test_id': 21,
1184 'test_class': FioJobTest_t0020,
1185 'job': 't0021.fio',
1186 'success': SUCCESS_DEFAULT,
1187 'pre_job': None,
1188 'pre_success': None,
1189 'requirements': [],
1190 },
1191 {
1192 'test_id': 22,
1193 'test_class': FioJobTest_t0022,
1194 'job': 't0022.fio',
1195 'success': SUCCESS_DEFAULT,
1196 'pre_job': None,
1197 'pre_success': None,
1198 'requirements': [],
1199 },
c37183f8
VF
1200 {
1201 'test_id': 23,
1202 'test_class': FioJobTest_t0023,
1203 'job': 't0023.fio',
1204 'success': SUCCESS_DEFAULT,
1205 'pre_job': None,
1206 'pre_success': None,
1207 'requirements': [],
1208 },
2d5c4600
VF
1209 {
1210 'test_id': 24,
1211 'test_class': FioJobTest_t0024,
1212 'job': 't0024.fio',
1213 'success': SUCCESS_DEFAULT,
1214 'pre_job': None,
1215 'pre_success': None,
1216 'requirements': [],
1217 },
d661fb18
SK
1218 {
1219 'test_id': 25,
1220 'test_class': FioJobTest_t0025,
1221 'job': 't0025.fio',
1222 'success': SUCCESS_DEFAULT,
1223 'pre_job': None,
1224 'pre_success': None,
1225 'output_format': 'json',
1226 'requirements': [],
1227 },
c4704c08
SK
1228 {
1229 'test_id': 26,
1230 'test_class': FioJobTest,
1231 'job': 't0026.fio',
1232 'success': SUCCESS_DEFAULT,
1233 'pre_job': None,
1234 'pre_success': None,
1235 'requirements': [Requirements.not_windows],
1236 },
ede04c27
LG
1237 {
1238 'test_id': 27,
1239 'test_class': FioJobTest_t0027,
1240 'job': 't0027.fio',
1241 'success': SUCCESS_DEFAULT,
1242 'pre_job': None,
1243 'pre_success': None,
1244 'requirements': [],
1245 },
704cc4df
VF
1246 {
1247 'test_id': 1000,
1248 'test_class': FioExeTest,
1249 'exe': 't/axmap',
1250 'parameters': None,
1251 'success': SUCCESS_DEFAULT,
1252 'requirements': [],
1253 },
1254 {
1255 'test_id': 1001,
1256 'test_class': FioExeTest,
1257 'exe': 't/ieee754',
1258 'parameters': None,
1259 'success': SUCCESS_DEFAULT,
1260 'requirements': [],
1261 },
1262 {
1263 'test_id': 1002,
1264 'test_class': FioExeTest,
1265 'exe': 't/lfsr-test',
1266 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
1267 'success': SUCCESS_STDERR,
1268 'requirements': [],
1269 },
1270 {
1271 'test_id': 1003,
1272 'test_class': FioExeTest,
1273 'exe': 't/readonly.py',
1274 'parameters': ['-f', '{fio_path}'],
1275 'success': SUCCESS_DEFAULT,
1276 'requirements': [],
1277 },
1278 {
1279 'test_id': 1004,
1280 'test_class': FioExeTest,
1281 'exe': 't/steadystate_tests.py',
1282 'parameters': ['{fio_path}'],
1283 'success': SUCCESS_DEFAULT,
1284 'requirements': [],
1285 },
1286 {
1287 'test_id': 1005,
1288 'test_class': FioExeTest,
1289 'exe': 't/stest',
1290 'parameters': None,
1291 'success': SUCCESS_STDERR,
1292 'requirements': [],
1293 },
1294 {
1295 'test_id': 1006,
1296 'test_class': FioExeTest,
1297 'exe': 't/strided.py',
1298 'parameters': ['{fio_path}'],
1299 'success': SUCCESS_DEFAULT,
1300 'requirements': [],
1301 },
1302 {
1303 'test_id': 1007,
1304 'test_class': FioExeTest,
037b2b50
DF
1305 'exe': 't/zbd/run-tests-against-nullb',
1306 'parameters': ['-s', '1'],
704cc4df
VF
1307 'success': SUCCESS_DEFAULT,
1308 'requirements': [Requirements.linux, Requirements.zbd,
1309 Requirements.root],
1310 },
1311 {
1312 'test_id': 1008,
1313 'test_class': FioExeTest,
037b2b50
DF
1314 'exe': 't/zbd/run-tests-against-nullb',
1315 'parameters': ['-s', '2'],
704cc4df
VF
1316 'success': SUCCESS_DEFAULT,
1317 'requirements': [Requirements.linux, Requirements.zbd,
1318 Requirements.root, Requirements.zoned_nullb],
1319 },
1320 {
1321 'test_id': 1009,
1322 'test_class': FioExeTest,
1323 'exe': 'unittests/unittest',
1324 'parameters': None,
1325 'success': SUCCESS_DEFAULT,
1326 'requirements': [Requirements.unittests],
1327 },
1328 {
1329 'test_id': 1010,
1330 'test_class': FioExeTest,
1331 'exe': 't/latency_percentiles.py',
1332 'parameters': ['-f', '{fio_path}'],
1333 'success': SUCCESS_DEFAULT,
1334 'requirements': [],
1335 },
8403eca6
VF
1336 {
1337 'test_id': 1011,
1338 'test_class': FioExeTest,
1339 'exe': 't/jsonplus2csv_test.py',
1340 'parameters': ['-f', '{fio_path}'],
1341 'success': SUCCESS_DEFAULT,
1342 'requirements': [],
1343 },
03900b0b 1344 {
1345 'test_id': 1012,
1346 'test_class': FioExeTest,
1347 'exe': 't/log_compression.py',
1348 'parameters': ['-f', '{fio_path}'],
1349 'success': SUCCESS_DEFAULT,
1350 'requirements': [],
1351 },
df1eaa36
VF
1352]
1353
1354
1355def parse_args():
704cc4df
VF
1356 """Parse command-line arguments."""
1357
df1eaa36
VF
1358 parser = argparse.ArgumentParser()
1359 parser.add_argument('-r', '--fio-root',
1360 help='fio root path')
1361 parser.add_argument('-f', '--fio',
1362 help='path to fio executable (e.g., ./fio)')
1363 parser.add_argument('-a', '--artifact-root',
1364 help='artifact root directory')
1365 parser.add_argument('-s', '--skip', nargs='+', type=int,
1366 help='list of test(s) to skip')
1367 parser.add_argument('-o', '--run-only', nargs='+', type=int,
1368 help='list of test(s) to run, skipping all others')
6d5470c3
VF
1369 parser.add_argument('-d', '--debug', action='store_true',
1370 help='provide debug output')
b1bc705e
VF
1371 parser.add_argument('-k', '--skip-req', action='store_true',
1372 help='skip requirements checking')
58a77d2a
VF
1373 parser.add_argument('-p', '--pass-through', action='append',
1374 help='pass-through an argument to an executable test')
df1eaa36
VF
1375 args = parser.parse_args()
1376
1377 return args
1378
1379
1380def main():
704cc4df
VF
1381 """Entry point."""
1382
df1eaa36 1383 args = parse_args()
6d5470c3
VF
1384 if args.debug:
1385 logging.basicConfig(level=logging.DEBUG)
1386 else:
1387 logging.basicConfig(level=logging.INFO)
1388
58a77d2a
VF
1389 pass_through = {}
1390 if args.pass_through:
1391 for arg in args.pass_through:
1392 if not ':' in arg:
1393 print("Invalid --pass-through argument '%s'" % arg)
1394 print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
1395 return
061a0773 1396 split = arg.split(":", 1)
58a77d2a 1397 pass_through[int(split[0])] = split[1]
061a0773 1398 logging.debug("Pass-through arguments: %s", pass_through)
58a77d2a 1399
df1eaa36
VF
1400 if args.fio_root:
1401 fio_root = args.fio_root
1402 else:
6d5470c3
VF
1403 fio_root = str(Path(__file__).absolute().parent.parent)
1404 print("fio root is %s" % fio_root)
df1eaa36
VF
1405
1406 if args.fio:
1407 fio_path = args.fio
1408 else:
742b8799
VF
1409 if platform.system() == "Windows":
1410 fio_exe = "fio.exe"
1411 else:
1412 fio_exe = "fio"
1413 fio_path = os.path.join(fio_root, fio_exe)
6d5470c3
VF
1414 print("fio path is %s" % fio_path)
1415 if not shutil.which(fio_path):
1416 print("Warning: fio executable not found")
df1eaa36
VF
1417
1418 artifact_root = args.artifact_root if args.artifact_root else \
1419 "fio-test-{0}".format(time.strftime("%Y%m%d-%H%M%S"))
1420 os.mkdir(artifact_root)
1421 print("Artifact directory is %s" % artifact_root)
1422
b1bc705e
VF
1423 if not args.skip_req:
1424 req = Requirements(fio_root)
1425
df1eaa36
VF
1426 passed = 0
1427 failed = 0
1428 skipped = 0
1429
1430 for config in TEST_LIST:
1431 if (args.skip and config['test_id'] in args.skip) or \
1432 (args.run_only and config['test_id'] not in args.run_only):
1433 skipped = skipped + 1
b1bc705e 1434 print("Test {0} SKIPPED (User request)".format(config['test_id']))
df1eaa36
VF
1435 continue
1436
1437 if issubclass(config['test_class'], FioJobTest):
1438 if config['pre_job']:
1439 fio_pre_job = os.path.join(fio_root, 't', 'jobs',
1440 config['pre_job'])
1441 else:
1442 fio_pre_job = None
1443 if config['pre_success']:
1444 fio_pre_success = config['pre_success']
1445 else:
1446 fio_pre_success = None
1447 if 'output_format' in config:
1448 output_format = config['output_format']
1449 else:
1450 output_format = 'normal'
1451 test = config['test_class'](
1452 fio_path,
1453 os.path.join(fio_root, 't', 'jobs', config['job']),
1454 config['success'],
1455 fio_pre_job=fio_pre_job,
1456 fio_pre_success=fio_pre_success,
1457 output_format=output_format)
9393cdaa 1458 desc = config['job']
df1eaa36
VF
1459 elif issubclass(config['test_class'], FioExeTest):
1460 exe_path = os.path.join(fio_root, config['exe'])
1461 if config['parameters']:
1462 parameters = [p.format(fio_path=fio_path) for p in config['parameters']]
1463 else:
58a77d2a 1464 parameters = []
742b8799 1465 if Path(exe_path).suffix == '.py' and platform.system() == "Windows":
58a77d2a 1466 parameters.insert(0, exe_path)
742b8799 1467 exe_path = "python.exe"
58a77d2a
VF
1468 if config['test_id'] in pass_through:
1469 parameters += pass_through[config['test_id']].split()
df1eaa36
VF
1470 test = config['test_class'](exe_path, parameters,
1471 config['success'])
9393cdaa 1472 desc = config['exe']
df1eaa36
VF
1473 else:
1474 print("Test {0} FAILED: unable to process test config".format(config['test_id']))
1475 failed = failed + 1
1476 continue
1477
b1bc705e 1478 if not args.skip_req:
704cc4df 1479 reqs_met = True
b1bc705e 1480 for req in config['requirements']:
704cc4df
VF
1481 reqs_met, reason = req()
1482 logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason,
1483 reqs_met)
1484 if not reqs_met:
b1bc705e 1485 break
704cc4df 1486 if not reqs_met:
9393cdaa 1487 print("Test {0} SKIPPED ({1}) {2}".format(config['test_id'], reason, desc))
b1bc705e
VF
1488 skipped = skipped + 1
1489 continue
1490
aa9f2627
VF
1491 try:
1492 test.setup(artifact_root, config['test_id'])
1493 test.run()
1494 test.check_result()
1495 except KeyboardInterrupt:
1496 break
1497 except Exception as e:
1498 test.passed = False
1499 test.failure_reason += str(e)
1500 logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc())
df1eaa36
VF
1501 if test.passed:
1502 result = "PASSED"
1503 passed = passed + 1
1504 else:
1505 result = "FAILED: {0}".format(test.failure_reason)
1506 failed = failed + 1
15a73987
VF
1507 contents, _ = FioJobTest.get_file(test.stderr_file)
1508 logging.debug("Test %d: stderr:\n%s", config['test_id'], contents)
1509 contents, _ = FioJobTest.get_file(test.stdout_file)
1510 logging.debug("Test %d: stdout:\n%s", config['test_id'], contents)
9393cdaa 1511 print("Test {0} {1} {2}".format(config['test_id'], result, desc))
df1eaa36
VF
1512
1513 print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))
1514
1515 sys.exit(failed)
1516
1517
1518if __name__ == '__main__':
1519 main()