gfio: add high/low priority latency results
[fio.git] / t / run-fio-tests.py
... / ...
CommitLineData
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
17# # git clone git://git.kernel.dk/fio.git
18# # cd fio
19# # make -j
20# # python3 t/run-fio-tests.py
21#
22#
23# REQUIREMENTS
24# - Python 3.5 (subprocess.run)
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)
42#
43
44import os
45import sys
46import json
47import time
48import shutil
49import logging
50import argparse
51import platform
52import subprocess
53import multiprocessing
54from pathlib import Path
55
56
57class FioTest(object):
58 """Base for all fio tests."""
59
60 def __init__(self, exe_path, parameters, success):
61 self.exe_path = exe_path
62 self.parameters = parameters
63 self.success = success
64 self.output = {}
65 self.artifact_root = None
66 self.testnum = None
67 self.test_dir = None
68 self.passed = True
69 self.failure_reason = ''
70
71 def setup(self, artifact_root, testnum):
72 self.artifact_root = artifact_root
73 self.testnum = testnum
74 self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum))
75 if not os.path.exists(self.test_dir):
76 os.mkdir(self.test_dir)
77
78 self.command_file = os.path.join(
79 self.test_dir,
80 "{0}.command".format(os.path.basename(self.exe_path)))
81 self.stdout_file = os.path.join(
82 self.test_dir,
83 "{0}.stdout".format(os.path.basename(self.exe_path)))
84 self.stderr_file = os.path.join(
85 self.test_dir,
86 "{0}.stderr".format(os.path.basename(self.exe_path)))
87 self.exticode_file = os.path.join(
88 self.test_dir,
89 "{0}.exitcode".format(os.path.basename(self.exe_path)))
90
91 def run(self):
92 raise NotImplementedError()
93
94 def check_result(self):
95 raise NotImplementedError()
96
97
98class FioExeTest(FioTest):
99 """Test consists of an executable binary or script"""
100
101 def __init__(self, exe_path, parameters, success):
102 """Construct a FioExeTest which is a FioTest consisting of an
103 executable binary or script.
104
105 exe_path: location of executable binary or script
106 parameters: list of parameters for executable
107 success: Definition of test success
108 """
109
110 FioTest.__init__(self, exe_path, parameters, success)
111
112 def setup(self, artifact_root, testnum):
113 super(FioExeTest, self).setup(artifact_root, testnum)
114
115 def run(self):
116 if self.parameters:
117 command = [self.exe_path] + self.parameters
118 else:
119 command = [self.exe_path]
120 command_file = open(self.command_file, "w+")
121 command_file.write("%s\n" % command)
122 command_file.close()
123
124 stdout_file = open(self.stdout_file, "w+")
125 stderr_file = open(self.stderr_file, "w+")
126 exticode_file = open(self.exticode_file, "w+")
127 try:
128 proc = None
129 # Avoid using subprocess.run() here because when a timeout occurs,
130 # fio will be stopped with SIGKILL. This does not give fio a
131 # chance to clean up and means that child processes may continue
132 # running and submitting IO.
133 proc = subprocess.Popen(command,
134 stdout=stdout_file,
135 stderr=stderr_file,
136 cwd=self.test_dir,
137 universal_newlines=True)
138 proc.communicate(timeout=self.success['timeout'])
139 exticode_file.write('{0}\n'.format(proc.returncode))
140 logging.debug("Test %d: return code: %d" % (self.testnum, proc.returncode))
141 self.output['proc'] = proc
142 except subprocess.TimeoutExpired:
143 proc.terminate()
144 proc.communicate()
145 assert proc.poll()
146 self.output['failure'] = 'timeout'
147 except Exception:
148 if proc:
149 if not proc.poll():
150 proc.terminate()
151 proc.communicate()
152 self.output['failure'] = 'exception'
153 self.output['exc_info'] = sys.exc_info()
154 finally:
155 stdout_file.close()
156 stderr_file.close()
157 exticode_file.close()
158
159 def check_result(self):
160 if 'proc' not in self.output:
161 if self.output['failure'] == 'timeout':
162 self.failure_reason = "{0} timeout,".format(self.failure_reason)
163 else:
164 assert self.output['failure'] == 'exception'
165 self.failure_reason = '{0} exception: {1}, {2}'.format(
166 self.failure_reason, self.output['exc_info'][0],
167 self.output['exc_info'][1])
168
169 self.passed = False
170 return
171
172 if 'zero_return' in self.success:
173 if self.success['zero_return']:
174 if self.output['proc'].returncode != 0:
175 self.passed = False
176 self.failure_reason = "{0} non-zero return code,".format(self.failure_reason)
177 else:
178 if self.output['proc'].returncode == 0:
179 self.failure_reason = "{0} zero return code,".format(self.failure_reason)
180 self.passed = False
181
182 stderr_size = os.path.getsize(self.stderr_file)
183 if 'stderr_empty' in self.success:
184 if self.success['stderr_empty']:
185 if stderr_size != 0:
186 self.failure_reason = "{0} stderr not empty,".format(self.failure_reason)
187 self.passed = False
188 else:
189 if stderr_size == 0:
190 self.failure_reason = "{0} stderr empty,".format(self.failure_reason)
191 self.passed = False
192
193
194class FioJobTest(FioExeTest):
195 """Test consists of a fio job"""
196
197 def __init__(self, fio_path, fio_job, success, fio_pre_job=None,
198 fio_pre_success=None, output_format="normal"):
199 """Construct a FioJobTest which is a FioExeTest consisting of a
200 single fio job file with an optional setup step.
201
202 fio_path: location of fio executable
203 fio_job: location of fio job file
204 success: Definition of test success
205 fio_pre_job: fio job for preconditioning
206 fio_pre_success: Definition of test success for fio precon job
207 output_format: normal (default), json, jsonplus, or terse
208 """
209
210 self.fio_job = fio_job
211 self.fio_pre_job = fio_pre_job
212 self.fio_pre_success = fio_pre_success if fio_pre_success else success
213 self.output_format = output_format
214 self.precon_failed = False
215 self.json_data = None
216 self.fio_output = "{0}.output".format(os.path.basename(self.fio_job))
217 self.fio_args = [
218 "--output-format={0}".format(self.output_format),
219 "--output={0}".format(self.fio_output),
220 self.fio_job,
221 ]
222 FioExeTest.__init__(self, fio_path, self.fio_args, success)
223
224 def setup(self, artifact_root, testnum):
225 super(FioJobTest, self).setup(artifact_root, testnum)
226
227 self.command_file = os.path.join(
228 self.test_dir,
229 "{0}.command".format(os.path.basename(self.fio_job)))
230 self.stdout_file = os.path.join(
231 self.test_dir,
232 "{0}.stdout".format(os.path.basename(self.fio_job)))
233 self.stderr_file = os.path.join(
234 self.test_dir,
235 "{0}.stderr".format(os.path.basename(self.fio_job)))
236 self.exticode_file = os.path.join(
237 self.test_dir,
238 "{0}.exitcode".format(os.path.basename(self.fio_job)))
239
240 def run_pre_job(self):
241 precon = FioJobTest(self.exe_path, self.fio_pre_job,
242 self.fio_pre_success,
243 output_format=self.output_format)
244 precon.setup(self.artifact_root, self.testnum)
245 precon.run()
246 precon.check_result()
247 self.precon_failed = not precon.passed
248 self.failure_reason = precon.failure_reason
249
250 def run(self):
251 if self.fio_pre_job:
252 self.run_pre_job()
253
254 if not self.precon_failed:
255 super(FioJobTest, self).run()
256 else:
257 logging.debug("Test %d: precondition step failed" % self.testnum)
258
259 def check_result(self):
260 if self.precon_failed:
261 self.passed = False
262 self.failure_reason = "{0} precondition step failed,".format(self.failure_reason)
263 return
264
265 super(FioJobTest, self).check_result()
266
267 if not self.passed:
268 return
269
270 if not 'json' in self.output_format:
271 return
272
273 try:
274 with open(os.path.join(self.test_dir, self.fio_output), "r") as output_file:
275 file_data = output_file.read()
276 except EnvironmentError:
277 self.failure_reason = "{0} unable to open output file,".format(self.failure_reason)
278 self.passed = False
279 return
280
281 #
282 # Sometimes fio informational messages are included at the top of the
283 # JSON output, especially under Windows. Try to decode output as JSON
284 # data, lopping off up to the first four lines
285 #
286 lines = file_data.splitlines()
287 for i in range(5):
288 file_data = '\n'.join(lines[i:])
289 try:
290 self.json_data = json.loads(file_data)
291 except json.JSONDecodeError:
292 continue
293 else:
294 logging.debug("Test %d: skipped %d lines decoding JSON data" % (self.testnum, i))
295 return
296
297 self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason)
298 self.passed = False
299
300
301class FioJobTest_t0005(FioJobTest):
302 """Test consists of fio test job t0005
303 Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400"""
304
305 def check_result(self):
306 super(FioJobTest_t0005, self).check_result()
307
308 if not self.passed:
309 return
310
311 if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400:
312 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
313 self.passed = False
314 if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400:
315 self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
316 self.passed = False
317
318
319class FioJobTest_t0006(FioJobTest):
320 """Test consists of fio test job t0006
321 Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']"""
322
323 def check_result(self):
324 super(FioJobTest_t0006, self).check_result()
325
326 if not self.passed:
327 return
328
329 ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
330 / self.json_data['jobs'][0]['write']['io_kbytes']
331 logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
332 if ratio < 1.99 or ratio > 2.01:
333 self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason)
334 self.passed = False
335
336
337class FioJobTest_t0007(FioJobTest):
338 """Test consists of fio test job t0007
339 Confirm that read['io_kbytes'] = 87040"""
340
341 def check_result(self):
342 super(FioJobTest_t0007, self).check_result()
343
344 if not self.passed:
345 return
346
347 if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040:
348 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
349 self.passed = False
350
351
352class FioJobTest_t0008(FioJobTest):
353 """Test consists of fio test job t0008
354 Confirm that read['io_kbytes'] = 32768 and that
355 write['io_kbytes'] ~ 16568
356
357 I did runs with fio-ae2fafc8 and saw write['io_kbytes'] values of
358 16585, 16588. With two runs of fio-3.16 I obtained 16568"""
359
360 def check_result(self):
361 super(FioJobTest_t0008, self).check_result()
362
363 if not self.passed:
364 return
365
366 ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568
367 logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
368
369 if ratio < 0.99 or ratio > 1.01:
370 self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
371 self.passed = False
372 if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768:
373 self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason)
374 self.passed = False
375
376
377class FioJobTest_t0009(FioJobTest):
378 """Test consists of fio test job t0009
379 Confirm that runtime >= 60s"""
380
381 def check_result(self):
382 super(FioJobTest_t0009, self).check_result()
383
384 if not self.passed:
385 return
386
387 logging.debug('Test %d: elapsed: %d' % (self.testnum, self.json_data['jobs'][0]['elapsed']))
388
389 if self.json_data['jobs'][0]['elapsed'] < 60:
390 self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason)
391 self.passed = False
392
393
394class FioJobTest_t0011(FioJobTest):
395 """Test consists of fio test job t0009
396 Confirm that job0 iops == 1000
397 and that job1_iops / job0_iops ~ 8
398 With two runs of fio-3.16 I observed a ratio of 8.3"""
399
400 def check_result(self):
401 super(FioJobTest_t0011, self).check_result()
402
403 if not self.passed:
404 return
405
406 iops1 = self.json_data['jobs'][0]['read']['iops']
407 iops2 = self.json_data['jobs'][1]['read']['iops']
408 ratio = iops2 / iops1
409 logging.debug("Test %d: iops1: %f" % (self.testnum, iops1))
410 logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
411
412 if iops1 < 998 or iops1 > 1002:
413 self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason)
414 self.passed = False
415
416 if ratio < 7 or ratio > 9:
417 self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason)
418 self.passed = False
419
420
421class Requirements(object):
422 """Requirements consists of multiple run environment characteristics.
423 These are to determine if a particular test can be run"""
424
425 _linux = False
426 _libaio = False
427 _zbd = False
428 _root = False
429 _zoned_nullb = False
430 _not_macos = False
431 _not_windows = False
432 _unittests = False
433 _cpucount4 = False
434
435 def __init__(self, fio_root):
436 Requirements._not_macos = platform.system() != "Darwin"
437 Requirements._not_windows = platform.system() != "Windows"
438 Requirements._linux = platform.system() == "Linux"
439
440 if Requirements._linux:
441 try:
442 config_file = os.path.join(fio_root, "config-host.h")
443 with open(config_file, "r") as config:
444 contents = config.read()
445 except Exception:
446 print("Unable to open {0} to check requirements".format(config_file))
447 Requirements._zbd = True
448 else:
449 Requirements._zbd = "CONFIG_LINUX_BLKZONED" in contents
450 Requirements._libaio = "CONFIG_LIBAIO" in contents
451
452 Requirements._root = (os.geteuid() == 0)
453 if Requirements._zbd and Requirements._root:
454 subprocess.run(["modprobe", "null_blk"],
455 stdout=subprocess.PIPE,
456 stderr=subprocess.PIPE)
457 if os.path.exists("/sys/module/null_blk/parameters/zoned"):
458 Requirements._zoned_nullb = True
459
460 if platform.system() == "Windows":
461 utest_exe = "unittest.exe"
462 else:
463 utest_exe = "unittest"
464 unittest_path = os.path.join(fio_root, "unittests", utest_exe)
465 Requirements._unittests = os.path.exists(unittest_path)
466
467 Requirements._cpucount4 = multiprocessing.cpu_count() >= 4
468
469 req_list = [Requirements.linux,
470 Requirements.libaio,
471 Requirements.zbd,
472 Requirements.root,
473 Requirements.zoned_nullb,
474 Requirements.not_macos,
475 Requirements.not_windows,
476 Requirements.unittests,
477 Requirements.cpucount4]
478 for req in req_list:
479 value, desc = req()
480 logging.debug("Requirements: Requirement '%s' met? %s" % (desc, value))
481
482 def linux():
483 return Requirements._linux, "Linux required"
484
485 def libaio():
486 return Requirements._libaio, "libaio required"
487
488 def zbd():
489 return Requirements._zbd, "Zoned block device support required"
490
491 def root():
492 return Requirements._root, "root required"
493
494 def zoned_nullb():
495 return Requirements._zoned_nullb, "Zoned null block device support required"
496
497 def not_macos():
498 return Requirements._not_macos, "platform other than macOS required"
499
500 def not_windows():
501 return Requirements._not_windows, "platform other than Windows required"
502
503 def unittests():
504 return Requirements._unittests, "Unittests support required"
505
506 def cpucount4():
507 return Requirements._cpucount4, "4+ CPUs required"
508
509
510SUCCESS_DEFAULT = {
511 'zero_return': True,
512 'stderr_empty': True,
513 'timeout': 600,
514 }
515SUCCESS_NONZERO = {
516 'zero_return': False,
517 'stderr_empty': False,
518 'timeout': 600,
519 }
520SUCCESS_STDERR = {
521 'zero_return': True,
522 'stderr_empty': False,
523 'timeout': 600,
524 }
525TEST_LIST = [
526 {
527 'test_id': 1,
528 'test_class': FioJobTest,
529 'job': 't0001-52c58027.fio',
530 'success': SUCCESS_DEFAULT,
531 'pre_job': None,
532 'pre_success': None,
533 'requirements': [],
534 },
535 {
536 'test_id': 2,
537 'test_class': FioJobTest,
538 'job': 't0002-13af05ae-post.fio',
539 'success': SUCCESS_DEFAULT,
540 'pre_job': 't0002-13af05ae-pre.fio',
541 'pre_success': None,
542 'requirements': [Requirements.linux, Requirements.libaio],
543 },
544 {
545 'test_id': 3,
546 'test_class': FioJobTest,
547 'job': 't0003-0ae2c6e1-post.fio',
548 'success': SUCCESS_NONZERO,
549 'pre_job': 't0003-0ae2c6e1-pre.fio',
550 'pre_success': SUCCESS_DEFAULT,
551 'requirements': [Requirements.linux, Requirements.libaio],
552 },
553 {
554 'test_id': 4,
555 'test_class': FioJobTest,
556 'job': 't0004-8a99fdf6.fio',
557 'success': SUCCESS_DEFAULT,
558 'pre_job': None,
559 'pre_success': None,
560 'requirements': [Requirements.linux, Requirements.libaio],
561 },
562 {
563 'test_id': 5,
564 'test_class': FioJobTest_t0005,
565 'job': 't0005-f7078f7b.fio',
566 'success': SUCCESS_DEFAULT,
567 'pre_job': None,
568 'pre_success': None,
569 'output_format': 'json',
570 'requirements': [Requirements.not_windows],
571 },
572 {
573 'test_id': 6,
574 'test_class': FioJobTest_t0006,
575 'job': 't0006-82af2a7c.fio',
576 'success': SUCCESS_DEFAULT,
577 'pre_job': None,
578 'pre_success': None,
579 'output_format': 'json',
580 'requirements': [Requirements.linux, Requirements.libaio],
581 },
582 {
583 'test_id': 7,
584 'test_class': FioJobTest_t0007,
585 'job': 't0007-37cf9e3c.fio',
586 'success': SUCCESS_DEFAULT,
587 'pre_job': None,
588 'pre_success': None,
589 'output_format': 'json',
590 'requirements': [],
591 },
592 {
593 'test_id': 8,
594 'test_class': FioJobTest_t0008,
595 'job': 't0008-ae2fafc8.fio',
596 'success': SUCCESS_DEFAULT,
597 'pre_job': None,
598 'pre_success': None,
599 'output_format': 'json',
600 'requirements': [],
601 },
602 {
603 'test_id': 9,
604 'test_class': FioJobTest_t0009,
605 'job': 't0009-f8b0bd10.fio',
606 'success': SUCCESS_DEFAULT,
607 'pre_job': None,
608 'pre_success': None,
609 'output_format': 'json',
610 'requirements': [Requirements.not_macos,
611 Requirements.cpucount4],
612 # mac os does not support CPU affinity
613 },
614 {
615 'test_id': 10,
616 'test_class': FioJobTest,
617 'job': 't0010-b7aae4ba.fio',
618 'success': SUCCESS_DEFAULT,
619 'pre_job': None,
620 'pre_success': None,
621 'requirements': [],
622 },
623 {
624 'test_id': 11,
625 'test_class': FioJobTest_t0011,
626 'job': 't0011-5d2788d5.fio',
627 'success': SUCCESS_DEFAULT,
628 'pre_job': None,
629 'pre_success': None,
630 'output_format': 'json',
631 'requirements': [],
632 },
633 {
634 'test_id': 1000,
635 'test_class': FioExeTest,
636 'exe': 't/axmap',
637 'parameters': None,
638 'success': SUCCESS_DEFAULT,
639 'requirements': [],
640 },
641 {
642 'test_id': 1001,
643 'test_class': FioExeTest,
644 'exe': 't/ieee754',
645 'parameters': None,
646 'success': SUCCESS_DEFAULT,
647 'requirements': [],
648 },
649 {
650 'test_id': 1002,
651 'test_class': FioExeTest,
652 'exe': 't/lfsr-test',
653 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
654 'success': SUCCESS_STDERR,
655 'requirements': [],
656 },
657 {
658 'test_id': 1003,
659 'test_class': FioExeTest,
660 'exe': 't/readonly.py',
661 'parameters': ['-f', '{fio_path}'],
662 'success': SUCCESS_DEFAULT,
663 'requirements': [],
664 },
665 {
666 'test_id': 1004,
667 'test_class': FioExeTest,
668 'exe': 't/steadystate_tests.py',
669 'parameters': ['{fio_path}'],
670 'success': SUCCESS_DEFAULT,
671 'requirements': [],
672 },
673 {
674 'test_id': 1005,
675 'test_class': FioExeTest,
676 'exe': 't/stest',
677 'parameters': None,
678 'success': SUCCESS_STDERR,
679 'requirements': [],
680 },
681 {
682 'test_id': 1006,
683 'test_class': FioExeTest,
684 'exe': 't/strided.py',
685 'parameters': ['{fio_path}'],
686 'success': SUCCESS_DEFAULT,
687 'requirements': [],
688 },
689 {
690 'test_id': 1007,
691 'test_class': FioExeTest,
692 'exe': 't/zbd/run-tests-against-regular-nullb',
693 'parameters': None,
694 'success': SUCCESS_DEFAULT,
695 'requirements': [Requirements.linux, Requirements.zbd,
696 Requirements.root],
697 },
698 {
699 'test_id': 1008,
700 'test_class': FioExeTest,
701 'exe': 't/zbd/run-tests-against-zoned-nullb',
702 'parameters': None,
703 'success': SUCCESS_DEFAULT,
704 'requirements': [Requirements.linux, Requirements.zbd,
705 Requirements.root, Requirements.zoned_nullb],
706 },
707 {
708 'test_id': 1009,
709 'test_class': FioExeTest,
710 'exe': 'unittests/unittest',
711 'parameters': None,
712 'success': SUCCESS_DEFAULT,
713 'requirements': [Requirements.unittests],
714 },
715 {
716 'test_id': 1010,
717 'test_class': FioExeTest,
718 'exe': 't/latency_percentiles.py',
719 'parameters': ['-f', '{fio_path}'],
720 'success': SUCCESS_DEFAULT,
721 'requirements': [],
722 },
723]
724
725
726def parse_args():
727 parser = argparse.ArgumentParser()
728 parser.add_argument('-r', '--fio-root',
729 help='fio root path')
730 parser.add_argument('-f', '--fio',
731 help='path to fio executable (e.g., ./fio)')
732 parser.add_argument('-a', '--artifact-root',
733 help='artifact root directory')
734 parser.add_argument('-s', '--skip', nargs='+', type=int,
735 help='list of test(s) to skip')
736 parser.add_argument('-o', '--run-only', nargs='+', type=int,
737 help='list of test(s) to run, skipping all others')
738 parser.add_argument('-d', '--debug', action='store_true',
739 help='provide debug output')
740 parser.add_argument('-k', '--skip-req', action='store_true',
741 help='skip requirements checking')
742 args = parser.parse_args()
743
744 return args
745
746
747def main():
748 args = parse_args()
749 if args.debug:
750 logging.basicConfig(level=logging.DEBUG)
751 else:
752 logging.basicConfig(level=logging.INFO)
753
754 if args.fio_root:
755 fio_root = args.fio_root
756 else:
757 fio_root = str(Path(__file__).absolute().parent.parent)
758 print("fio root is %s" % fio_root)
759
760 if args.fio:
761 fio_path = args.fio
762 else:
763 if platform.system() == "Windows":
764 fio_exe = "fio.exe"
765 else:
766 fio_exe = "fio"
767 fio_path = os.path.join(fio_root, fio_exe)
768 print("fio path is %s" % fio_path)
769 if not shutil.which(fio_path):
770 print("Warning: fio executable not found")
771
772 artifact_root = args.artifact_root if args.artifact_root else \
773 "fio-test-{0}".format(time.strftime("%Y%m%d-%H%M%S"))
774 os.mkdir(artifact_root)
775 print("Artifact directory is %s" % artifact_root)
776
777 if not args.skip_req:
778 req = Requirements(fio_root)
779
780 passed = 0
781 failed = 0
782 skipped = 0
783
784 for config in TEST_LIST:
785 if (args.skip and config['test_id'] in args.skip) or \
786 (args.run_only and config['test_id'] not in args.run_only):
787 skipped = skipped + 1
788 print("Test {0} SKIPPED (User request)".format(config['test_id']))
789 continue
790
791 if issubclass(config['test_class'], FioJobTest):
792 if config['pre_job']:
793 fio_pre_job = os.path.join(fio_root, 't', 'jobs',
794 config['pre_job'])
795 else:
796 fio_pre_job = None
797 if config['pre_success']:
798 fio_pre_success = config['pre_success']
799 else:
800 fio_pre_success = None
801 if 'output_format' in config:
802 output_format = config['output_format']
803 else:
804 output_format = 'normal'
805 test = config['test_class'](
806 fio_path,
807 os.path.join(fio_root, 't', 'jobs', config['job']),
808 config['success'],
809 fio_pre_job=fio_pre_job,
810 fio_pre_success=fio_pre_success,
811 output_format=output_format)
812 elif issubclass(config['test_class'], FioExeTest):
813 exe_path = os.path.join(fio_root, config['exe'])
814 if config['parameters']:
815 parameters = [p.format(fio_path=fio_path) for p in config['parameters']]
816 else:
817 parameters = None
818 if Path(exe_path).suffix == '.py' and platform.system() == "Windows":
819 if parameters:
820 parameters.insert(0, exe_path)
821 else:
822 parameters = [exe_path]
823 exe_path = "python.exe"
824 test = config['test_class'](exe_path, parameters,
825 config['success'])
826 else:
827 print("Test {0} FAILED: unable to process test config".format(config['test_id']))
828 failed = failed + 1
829 continue
830
831 if not args.skip_req:
832 skip = False
833 for req in config['requirements']:
834 ok, reason = req()
835 skip = not ok
836 logging.debug("Test %d: Requirement '%s' met? %s" % (config['test_id'], reason, ok))
837 if skip:
838 break
839 if skip:
840 print("Test {0} SKIPPED ({1})".format(config['test_id'], reason))
841 skipped = skipped + 1
842 continue
843
844 test.setup(artifact_root, config['test_id'])
845 test.run()
846 test.check_result()
847 if test.passed:
848 result = "PASSED"
849 passed = passed + 1
850 else:
851 result = "FAILED: {0}".format(test.failure_reason)
852 failed = failed + 1
853 with open(test.stderr_file, "r") as stderr_file:
854 logging.debug("Test %d: stderr:\n%s" % (config['test_id'], stderr_file.read()))
855 with open(test.stdout_file, "r") as stdout_file:
856 logging.debug("Test %d: stdout:\n%s" % (config['test_id'], stdout_file.read()))
857 print("Test {0} {1}".format(config['test_id'], result))
858
859 print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))
860
861 sys.exit(failed)
862
863
864if __name__ == '__main__':
865 main()