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