import logging
import argparse
import platform
+import traceback
import subprocess
import multiprocessing
from pathlib import Path
self.test_dir = None
self.passed = True
self.failure_reason = ''
+ self.command_file = None
+ self.stdout_file = None
+ self.stderr_file = None
+ self.exitcode_file = None
def setup(self, artifact_root, testnum):
+ """Setup instance variables for test."""
+
self.artifact_root = artifact_root
self.testnum = testnum
self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum))
os.mkdir(self.test_dir)
self.command_file = os.path.join(
- self.test_dir,
- "{0}.command".format(os.path.basename(self.exe_path)))
+ self.test_dir,
+ "{0}.command".format(os.path.basename(self.exe_path)))
self.stdout_file = os.path.join(
- self.test_dir,
- "{0}.stdout".format(os.path.basename(self.exe_path)))
+ self.test_dir,
+ "{0}.stdout".format(os.path.basename(self.exe_path)))
self.stderr_file = os.path.join(
- self.test_dir,
- "{0}.stderr".format(os.path.basename(self.exe_path)))
- self.exticode_file = os.path.join(
- self.test_dir,
- "{0}.exitcode".format(os.path.basename(self.exe_path)))
+ self.test_dir,
+ "{0}.stderr".format(os.path.basename(self.exe_path)))
+ self.exitcode_file = os.path.join(
+ self.test_dir,
+ "{0}.exitcode".format(os.path.basename(self.exe_path)))
def run(self):
+ """Run the test."""
+
raise NotImplementedError()
def check_result(self):
+ """Check test results."""
+
raise NotImplementedError()
FioTest.__init__(self, exe_path, parameters, success)
- def setup(self, artifact_root, testnum):
- super(FioExeTest, self).setup(artifact_root, testnum)
-
def run(self):
- if self.parameters:
- command = [self.exe_path] + self.parameters
- else:
- command = [self.exe_path]
+ """Execute the binary or script described by this instance."""
+
+ command = [self.exe_path] + self.parameters
command_file = open(self.command_file, "w+")
command_file.write("%s\n" % command)
command_file.close()
stdout_file = open(self.stdout_file, "w+")
stderr_file = open(self.stderr_file, "w+")
- exticode_file = open(self.exticode_file, "w+")
+ exitcode_file = open(self.exitcode_file, "w+")
try:
proc = None
# Avoid using subprocess.run() here because when a timeout occurs,
cwd=self.test_dir,
universal_newlines=True)
proc.communicate(timeout=self.success['timeout'])
- exticode_file.write('{0}\n'.format(proc.returncode))
- logging.debug("Test %d: return code: %d" % (self.testnum, proc.returncode))
+ exitcode_file.write('{0}\n'.format(proc.returncode))
+ logging.debug("Test %d: return code: %d", self.testnum, proc.returncode)
self.output['proc'] = proc
except subprocess.TimeoutExpired:
proc.terminate()
finally:
stdout_file.close()
stderr_file.close()
- exticode_file.close()
+ exitcode_file.close()
def check_result(self):
+ """Check results of test run."""
+
if 'proc' not in self.output:
if self.output['failure'] == 'timeout':
self.failure_reason = "{0} timeout,".format(self.failure_reason)
else:
assert self.output['failure'] == 'exception'
self.failure_reason = '{0} exception: {1}, {2}'.format(
- self.failure_reason, self.output['exc_info'][0],
- self.output['exc_info'][1])
+ self.failure_reason, self.output['exc_info'][0],
+ self.output['exc_info'][1])
self.passed = False
return
self.json_data = None
self.fio_output = "{0}.output".format(os.path.basename(self.fio_job))
self.fio_args = [
+ "--max-jobs=16",
"--output-format={0}".format(self.output_format),
"--output={0}".format(self.fio_output),
self.fio_job,
FioExeTest.__init__(self, fio_path, self.fio_args, success)
def setup(self, artifact_root, testnum):
+ """Setup instance variables for fio job test."""
+
super(FioJobTest, self).setup(artifact_root, testnum)
self.command_file = os.path.join(
- self.test_dir,
- "{0}.command".format(os.path.basename(self.fio_job)))
+ self.test_dir,
+ "{0}.command".format(os.path.basename(self.fio_job)))
self.stdout_file = os.path.join(
- self.test_dir,
- "{0}.stdout".format(os.path.basename(self.fio_job)))
+ self.test_dir,
+ "{0}.stdout".format(os.path.basename(self.fio_job)))
self.stderr_file = os.path.join(
- self.test_dir,
- "{0}.stderr".format(os.path.basename(self.fio_job)))
- self.exticode_file = os.path.join(
- self.test_dir,
- "{0}.exitcode".format(os.path.basename(self.fio_job)))
+ self.test_dir,
+ "{0}.stderr".format(os.path.basename(self.fio_job)))
+ self.exitcode_file = os.path.join(
+ self.test_dir,
+ "{0}.exitcode".format(os.path.basename(self.fio_job)))
def run_pre_job(self):
+ """Run fio job precondition step."""
+
precon = FioJobTest(self.exe_path, self.fio_pre_job,
self.fio_pre_success,
output_format=self.output_format)
self.failure_reason = precon.failure_reason
def run(self):
+ """Run fio job test."""
+
if self.fio_pre_job:
self.run_pre_job()
if not self.precon_failed:
super(FioJobTest, self).run()
else:
- logging.debug("Test %d: precondition step failed" % self.testnum)
+ logging.debug("Test %d: precondition step failed", self.testnum)
+
+ @classmethod
+ def get_file(cls, filename):
+ """Safely read a file."""
+ file_data = ''
+ success = True
+
+ try:
+ with open(filename, "r") as output_file:
+ file_data = output_file.read()
+ except OSError:
+ success = False
+
+ return file_data, success
def check_result(self):
+ """Check fio job results."""
+
if self.precon_failed:
self.passed = False
self.failure_reason = "{0} precondition step failed,".format(self.failure_reason)
if not self.passed:
return
- if not 'json' in self.output_format:
+ if 'json' not in self.output_format:
return
- try:
- with open(os.path.join(self.test_dir, self.fio_output), "r") as output_file:
- file_data = output_file.read()
- except EnvironmentError:
+ file_data, success = self.get_file(os.path.join(self.test_dir, self.fio_output))
+ if not success:
self.failure_reason = "{0} unable to open output file,".format(self.failure_reason)
self.passed = False
return
#
# Sometimes fio informational messages are included at the top of the
# JSON output, especially under Windows. Try to decode output as JSON
- # data, lopping off up to the first four lines
+ # data, skipping everything until the first {
#
lines = file_data.splitlines()
- for i in range(5):
- file_data = '\n'.join(lines[i:])
- try:
- self.json_data = json.loads(file_data)
- except json.JSONDecodeError:
- continue
- else:
- logging.debug("Test %d: skipped %d lines decoding JSON data" % (self.testnum, i))
- return
-
- self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason)
- self.passed = False
+ file_data = '\n'.join(lines[lines.index("{"):])
+ try:
+ self.json_data = json.loads(file_data)
+ except json.JSONDecodeError:
+ self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason)
+ self.passed = False
class FioJobTest_t0005(FioJobTest):
ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \
/ self.json_data['jobs'][0]['write']['io_kbytes']
- logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+ logging.debug("Test %d: ratio: %f", self.testnum, ratio)
if ratio < 1.99 or ratio > 2.01:
self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason)
self.passed = False
return
ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568
- logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+ logging.debug("Test %d: ratio: %f", self.testnum, ratio)
if ratio < 0.99 or ratio > 1.01:
self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason)
if not self.passed:
return
- logging.debug('Test %d: elapsed: %d' % (self.testnum, self.json_data['jobs'][0]['elapsed']))
+ logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed'])
if self.json_data['jobs'][0]['elapsed'] < 60:
self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason)
self.passed = False
-class FioJobTest_t0011(FioJobTest):
+class FioJobTest_t0012(FioJobTest):
+ """Test consists of fio test job t0012
+ Confirm ratios of job iops are 1:5:10
+ job1,job2,job3 respectively"""
+
+ def check_result(self):
+ super(FioJobTest_t0012, self).check_result()
+
+ if not self.passed:
+ return
+
+ iops_files = []
+ for i in range(1,4):
+ file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i)))
+
+ if not success:
+ self.failure_reason = "{0} unable to open output file,".format(self.failure_reason)
+ self.passed = False
+ return
+
+ iops_files.append(file_data.splitlines())
+
+ # there are 9 samples for job1 and job2, 4 samples for job3
+ iops1 = 0.0
+ iops2 = 0.0
+ iops3 = 0.0
+ for i in range(9):
+ iops1 = iops1 + float(iops_files[0][i].split(',')[1])
+ iops2 = iops2 + float(iops_files[1][i].split(',')[1])
+ iops3 = iops3 + float(iops_files[2][i].split(',')[1])
+
+ ratio1 = iops3/iops2
+ ratio2 = iops3/iops1
+ logging.debug(
+ "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job3/job2={4:.3f} job3/job1={5:.3f}".format(
+ i, iops1, iops2, iops3, ratio1, ratio2
+ )
+ )
+
+ # test job1 and job2 succeeded to recalibrate
+ if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13:
+ 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(
+ self.failure_reason, iops1, iops2, iops3, ratio1, ratio2
+ )
+ self.passed = False
+ return
+
+
+class FioJobTest_t0014(FioJobTest):
+ """Test consists of fio test job t0014
+ Confirm that job1_iops / job2_iops ~ 1:2 for entire duration
+ and that job1_iops / job3_iops ~ 1:3 for first half of duration.
+
+ The test is about making sure the flow feature can
+ re-calibrate the activity dynamically"""
+
+ def check_result(self):
+ super(FioJobTest_t0014, self).check_result()
+
+ if not self.passed:
+ return
+
+ iops_files = []
+ for i in range(1,4):
+ file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i)))
+
+ if not success:
+ self.failure_reason = "{0} unable to open output file,".format(self.failure_reason)
+ self.passed = False
+ return
+
+ iops_files.append(file_data.splitlines())
+
+ # there are 9 samples for job1 and job2, 4 samples for job3
+ iops1 = 0.0
+ iops2 = 0.0
+ iops3 = 0.0
+ for i in range(9):
+ if i < 4:
+ iops3 = iops3 + float(iops_files[2][i].split(',')[1])
+ elif i == 4:
+ ratio1 = iops1 / iops2
+ ratio2 = iops1 / iops3
+
+
+ if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45:
+ self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} iops3={3}\
+ expected r1~0.5 r2~0.33 got r1={4:.3f} r2={5:.3f},".format(
+ self.failure_reason, iops1, iops2, iops3, ratio1, ratio2
+ )
+ self.passed = False
+
+ iops1 = iops1 + float(iops_files[0][i].split(',')[1])
+ iops2 = iops2 + float(iops_files[1][i].split(',')[1])
+
+ ratio1 = iops1/iops2
+ ratio2 = iops1/iops3
+ logging.debug(
+ "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job1/job2={4:.3f} job1/job3={5:.3f}".format(
+ i, iops1, iops2, iops3, ratio1, ratio2
+ )
+ )
+
+ # test job1 and job2 succeeded to recalibrate
+ if ratio1 < 0.43 or ratio1 > 0.57:
+ self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} expected ratio~0.5 got ratio={3:.3f},".format(
+ self.failure_reason, iops1, iops2, ratio1
+ )
+ self.passed = False
+ return
+
+
+class FioJobTest_t0015(FioJobTest):
+ """Test consists of fio test jobs t0015 and t0016
+ Confirm that mean(slat) + mean(clat) = mean(tlat)"""
+
+ def check_result(self):
+ super(FioJobTest_t0015, self).check_result()
+
+ if not self.passed:
+ return
+
+ slat = self.json_data['jobs'][0]['read']['slat_ns']['mean']
+ clat = self.json_data['jobs'][0]['read']['clat_ns']['mean']
+ tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean']
+ logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat)
+
+ if abs(slat + clat - tlat) > 1:
+ self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format(
+ self.failure_reason, slat, clat, slat+clat, tlat)
+ self.passed = False
+
+
+class FioJobTest_t0019(FioJobTest):
+ """Test consists of fio test job t0019
+ Confirm that all offsets were touched sequentially"""
+
+ def check_result(self):
+ super(FioJobTest_t0019, self).check_result()
+
+ bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
+ file_data, success = self.get_file(bw_log_filename)
+ log_lines = file_data.split('\n')
+
+ prev = -4096
+ for line in log_lines:
+ if len(line.strip()) == 0:
+ continue
+ cur = int(line.split(',')[4])
+ if cur - prev != 4096:
+ self.passed = False
+ self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur)
+ return
+ prev = cur
+
+ if cur/4096 != 255:
+ self.passed = False
+ self.failure_reason = "unexpected last offset {0}".format(cur)
+
+
+class FioJobTest_t0020(FioJobTest):
+ """Test consists of fio test job t0020
+ Confirm that almost all offsets were touched non-sequentially"""
+
+ def check_result(self):
+ super(FioJobTest_t0020, self).check_result()
+
+ bw_log_filename = os.path.join(self.test_dir, "test_bw.log")
+ file_data, success = self.get_file(bw_log_filename)
+ log_lines = file_data.split('\n')
+
+ seq_count = 0
+ offsets = set()
+
+ prev = int(log_lines[0].split(',')[4])
+ for line in log_lines[1:]:
+ offsets.add(prev/4096)
+ if len(line.strip()) == 0:
+ continue
+ cur = int(line.split(',')[4])
+ if cur - prev == 4096:
+ seq_count += 1
+ prev = cur
+
+ # 10 is an arbitrary threshold
+ if seq_count > 10:
+ self.passed = False
+ self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count)
+
+ if len(offsets) != 256:
+ self.passed = False
+ self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets))
+
+ for i in range(256):
+ if not i in offsets:
+ self.passed = False
+ self.failure_reason += " missing offset {0}".format(i*4096)
+
+
+class FioJobTest_iops_rate(FioJobTest):
"""Test consists of fio test job t0009
Confirm that job0 iops == 1000
and that job1_iops / job0_iops ~ 8
With two runs of fio-3.16 I observed a ratio of 8.3"""
def check_result(self):
- super(FioJobTest_t0011, self).check_result()
+ super(FioJobTest_iops_rate, self).check_result()
if not self.passed:
return
iops1 = self.json_data['jobs'][0]['read']['iops']
+ logging.debug("Test %d: iops1: %f", self.testnum, iops1)
iops2 = self.json_data['jobs'][1]['read']['iops']
+ logging.debug("Test %d: iops2: %f", self.testnum, iops2)
ratio = iops2 / iops1
- logging.debug("Test %d: iops1: %f" % (self.testnum, iops1))
- logging.debug("Test %d: ratio: %f" % (self.testnum, ratio))
+ logging.debug("Test %d: ratio: %f", self.testnum, ratio)
- if iops1 < 998 or iops1 > 1002:
+ if iops1 < 950 or iops1 > 1050:
self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason)
self.passed = False
- if ratio < 7 or ratio > 9:
+ if ratio < 6 or ratio > 10:
self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason)
self.passed = False
_linux = False
_libaio = False
+ _io_uring = False
_zbd = False
_root = False
_zoned_nullb = False
Requirements._linux = platform.system() == "Linux"
if Requirements._linux:
- try:
- config_file = os.path.join(fio_root, "config-host.h")
- with open(config_file, "r") as config:
- contents = config.read()
- except Exception:
+ config_file = os.path.join(fio_root, "config-host.h")
+ contents, success = FioJobTest.get_file(config_file)
+ if not success:
print("Unable to open {0} to check requirements".format(config_file))
Requirements._zbd = True
else:
- Requirements._zbd = "CONFIG_LINUX_BLKZONED" in contents
+ Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents
Requirements._libaio = "CONFIG_LIBAIO" in contents
+ contents, success = FioJobTest.get_file("/proc/kallsyms")
+ if not success:
+ print("Unable to open '/proc/kallsyms' to probe for io_uring support")
+ else:
+ Requirements._io_uring = "io_uring_setup" in contents
+
Requirements._root = (os.geteuid() == 0)
if Requirements._zbd and Requirements._root:
+ try:
subprocess.run(["modprobe", "null_blk"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if os.path.exists("/sys/module/null_blk/parameters/zoned"):
Requirements._zoned_nullb = True
+ except Exception:
+ pass
if platform.system() == "Windows":
utest_exe = "unittest.exe"
req_list = [Requirements.linux,
Requirements.libaio,
+ Requirements.io_uring,
Requirements.zbd,
Requirements.root,
Requirements.zoned_nullb,
Requirements.cpucount4]
for req in req_list:
value, desc = req()
- logging.debug("Requirements: Requirement '%s' met? %s" % (desc, value))
+ logging.debug("Requirements: Requirement '%s' met? %s", desc, value)
- def linux():
+ @classmethod
+ def linux(cls):
+ """Are we running on Linux?"""
return Requirements._linux, "Linux required"
- def libaio():
+ @classmethod
+ def libaio(cls):
+ """Is libaio available?"""
return Requirements._libaio, "libaio required"
- def zbd():
+ @classmethod
+ def io_uring(cls):
+ """Is io_uring available?"""
+ return Requirements._io_uring, "io_uring required"
+
+ @classmethod
+ def zbd(cls):
+ """Is ZBD support available?"""
return Requirements._zbd, "Zoned block device support required"
- def root():
+ @classmethod
+ def root(cls):
+ """Are we running as root?"""
return Requirements._root, "root required"
- def zoned_nullb():
+ @classmethod
+ def zoned_nullb(cls):
+ """Are zoned null block devices available?"""
return Requirements._zoned_nullb, "Zoned null block device support required"
- def not_macos():
+ @classmethod
+ def not_macos(cls):
+ """Are we running on a platform other than macOS?"""
return Requirements._not_macos, "platform other than macOS required"
- def not_windows():
+ @classmethod
+ def not_windows(cls):
+ """Are we running on a platform other than Windws?"""
return Requirements._not_windows, "platform other than Windows required"
- def unittests():
+ @classmethod
+ def unittests(cls):
+ """Were unittests built?"""
return Requirements._unittests, "Unittests support required"
- def cpucount4():
+ @classmethod
+ def cpucount4(cls):
+ """Do we have at least 4 CPUs?"""
return Requirements._cpucount4, "4+ CPUs required"
SUCCESS_DEFAULT = {
- 'zero_return': True,
- 'stderr_empty': True,
- 'timeout': 600,
- }
+ 'zero_return': True,
+ 'stderr_empty': True,
+ 'timeout': 600,
+ }
SUCCESS_NONZERO = {
- 'zero_return': False,
- 'stderr_empty': False,
- 'timeout': 600,
- }
+ 'zero_return': False,
+ 'stderr_empty': False,
+ 'timeout': 600,
+ }
SUCCESS_STDERR = {
- 'zero_return': True,
- 'stderr_empty': False,
- 'timeout': 600,
- }
+ 'zero_return': True,
+ 'stderr_empty': False,
+ 'timeout': 600,
+ }
TEST_LIST = [
- {
- 'test_id': 1,
- 'test_class': FioJobTest,
- 'job': 't0001-52c58027.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'requirements': [],
- },
- {
- 'test_id': 2,
- 'test_class': FioJobTest,
- 'job': 't0002-13af05ae-post.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': 't0002-13af05ae-pre.fio',
- 'pre_success': None,
- 'requirements': [Requirements.linux, Requirements.libaio],
- },
- {
- 'test_id': 3,
- 'test_class': FioJobTest,
- 'job': 't0003-0ae2c6e1-post.fio',
- 'success': SUCCESS_NONZERO,
- 'pre_job': 't0003-0ae2c6e1-pre.fio',
- 'pre_success': SUCCESS_DEFAULT,
- 'requirements': [Requirements.linux, Requirements.libaio],
- },
- {
- 'test_id': 4,
- 'test_class': FioJobTest,
- 'job': 't0004-8a99fdf6.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'requirements': [Requirements.linux, Requirements.libaio],
- },
- {
- 'test_id': 5,
- 'test_class': FioJobTest_t0005,
- 'job': 't0005-f7078f7b.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [Requirements.not_windows],
- },
- {
- 'test_id': 6,
- 'test_class': FioJobTest_t0006,
- 'job': 't0006-82af2a7c.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [Requirements.linux, Requirements.libaio],
- },
- {
- 'test_id': 7,
- 'test_class': FioJobTest_t0007,
- 'job': 't0007-37cf9e3c.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [],
- },
- {
- 'test_id': 8,
- 'test_class': FioJobTest_t0008,
- 'job': 't0008-ae2fafc8.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [],
- },
- {
- 'test_id': 9,
- 'test_class': FioJobTest_t0009,
- 'job': 't0009-f8b0bd10.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [Requirements.not_macos,
- Requirements.cpucount4],
- # mac os does not support CPU affinity
- },
- {
- 'test_id': 10,
- 'test_class': FioJobTest,
- 'job': 't0010-b7aae4ba.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'requirements': [],
- },
- {
- 'test_id': 11,
- 'test_class': FioJobTest_t0011,
- 'job': 't0011-5d2788d5.fio',
- 'success': SUCCESS_DEFAULT,
- 'pre_job': None,
- 'pre_success': None,
- 'output_format': 'json',
- 'requirements': [],
- },
- {
- 'test_id': 1000,
- 'test_class': FioExeTest,
- 'exe': 't/axmap',
- 'parameters': None,
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
- {
- 'test_id': 1001,
- 'test_class': FioExeTest,
- 'exe': 't/ieee754',
- 'parameters': None,
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
- {
- 'test_id': 1002,
- 'test_class': FioExeTest,
- 'exe': 't/lfsr-test',
- 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
- 'success': SUCCESS_STDERR,
- 'requirements': [],
- },
- {
- 'test_id': 1003,
- 'test_class': FioExeTest,
- 'exe': 't/readonly.py',
- 'parameters': ['-f', '{fio_path}'],
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
- {
- 'test_id': 1004,
- 'test_class': FioExeTest,
- 'exe': 't/steadystate_tests.py',
- 'parameters': ['{fio_path}'],
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
- {
- 'test_id': 1005,
- 'test_class': FioExeTest,
- 'exe': 't/stest',
- 'parameters': None,
- 'success': SUCCESS_STDERR,
- 'requirements': [],
- },
- {
- 'test_id': 1006,
- 'test_class': FioExeTest,
- 'exe': 't/strided.py',
- 'parameters': ['{fio_path}'],
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
- {
- 'test_id': 1007,
- 'test_class': FioExeTest,
- 'exe': 't/zbd/run-tests-against-regular-nullb',
- 'parameters': None,
- 'success': SUCCESS_DEFAULT,
- 'requirements': [Requirements.linux, Requirements.zbd,
- Requirements.root],
- },
- {
- 'test_id': 1008,
- 'test_class': FioExeTest,
- 'exe': 't/zbd/run-tests-against-zoned-nullb',
- 'parameters': None,
- 'success': SUCCESS_DEFAULT,
- 'requirements': [Requirements.linux, Requirements.zbd,
- Requirements.root, Requirements.zoned_nullb],
- },
- {
- 'test_id': 1009,
- 'test_class': FioExeTest,
- 'exe': 'unittests/unittest',
- 'parameters': None,
- 'success': SUCCESS_DEFAULT,
- 'requirements': [Requirements.unittests],
- },
- {
- 'test_id': 1010,
- 'test_class': FioExeTest,
- 'exe': 't/latency_percentiles.py',
- 'parameters': ['-f', '{fio_path}'],
- 'success': SUCCESS_DEFAULT,
- 'requirements': [],
- },
+ {
+ 'test_id': 1,
+ 'test_class': FioJobTest,
+ 'job': 't0001-52c58027.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 2,
+ 'test_class': FioJobTest,
+ 'job': 't0002-13af05ae-post.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': 't0002-13af05ae-pre.fio',
+ 'pre_success': None,
+ 'requirements': [Requirements.linux, Requirements.libaio],
+ },
+ {
+ 'test_id': 3,
+ 'test_class': FioJobTest,
+ 'job': 't0003-0ae2c6e1-post.fio',
+ 'success': SUCCESS_NONZERO,
+ 'pre_job': 't0003-0ae2c6e1-pre.fio',
+ 'pre_success': SUCCESS_DEFAULT,
+ 'requirements': [Requirements.linux, Requirements.libaio],
+ },
+ {
+ 'test_id': 4,
+ 'test_class': FioJobTest,
+ 'job': 't0004-8a99fdf6.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [Requirements.linux, Requirements.libaio],
+ },
+ {
+ 'test_id': 5,
+ 'test_class': FioJobTest_t0005,
+ 'job': 't0005-f7078f7b.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [Requirements.not_windows],
+ },
+ {
+ 'test_id': 6,
+ 'test_class': FioJobTest_t0006,
+ 'job': 't0006-82af2a7c.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [Requirements.linux, Requirements.libaio],
+ },
+ {
+ 'test_id': 7,
+ 'test_class': FioJobTest_t0007,
+ 'job': 't0007-37cf9e3c.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 8,
+ 'test_class': FioJobTest_t0008,
+ 'job': 't0008-ae2fafc8.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 9,
+ 'test_class': FioJobTest_t0009,
+ 'job': 't0009-f8b0bd10.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [Requirements.not_macos,
+ Requirements.cpucount4],
+ # mac os does not support CPU affinity
+ },
+ {
+ 'test_id': 10,
+ 'test_class': FioJobTest,
+ 'job': 't0010-b7aae4ba.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 11,
+ 'test_class': FioJobTest_iops_rate,
+ 'job': 't0011-5d2788d5.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 12,
+ 'test_class': FioJobTest_t0012,
+ 'job': 't0012.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 13,
+ 'test_class': FioJobTest,
+ 'job': 't0013.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 14,
+ 'test_class': FioJobTest_t0014,
+ 'job': 't0014.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 15,
+ 'test_class': FioJobTest_t0015,
+ 'job': 't0015-e78980ff.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [Requirements.linux, Requirements.libaio],
+ },
+ {
+ 'test_id': 16,
+ 'test_class': FioJobTest_t0015,
+ 'job': 't0016-d54ae22.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [],
+ },
+ {
+ 'test_id': 17,
+ 'test_class': FioJobTest_t0015,
+ 'job': 't0017.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'output_format': 'json',
+ 'requirements': [Requirements.not_windows],
+ },
+ {
+ 'test_id': 18,
+ 'test_class': FioJobTest,
+ 'job': 't0018.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [Requirements.linux, Requirements.io_uring],
+ },
+ {
+ 'test_id': 19,
+ 'test_class': FioJobTest_t0019,
+ 'job': 't0019.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 20,
+ 'test_class': FioJobTest_t0020,
+ 'job': 't0020.fio',
+ 'success': SUCCESS_DEFAULT,
+ 'pre_job': None,
+ 'pre_success': None,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1000,
+ 'test_class': FioExeTest,
+ 'exe': 't/axmap',
+ 'parameters': None,
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1001,
+ 'test_class': FioExeTest,
+ 'exe': 't/ieee754',
+ 'parameters': None,
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1002,
+ 'test_class': FioExeTest,
+ 'exe': 't/lfsr-test',
+ 'parameters': ['0xFFFFFF', '0', '0', 'verify'],
+ 'success': SUCCESS_STDERR,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1003,
+ 'test_class': FioExeTest,
+ 'exe': 't/readonly.py',
+ 'parameters': ['-f', '{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1004,
+ 'test_class': FioExeTest,
+ 'exe': 't/steadystate_tests.py',
+ 'parameters': ['{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1005,
+ 'test_class': FioExeTest,
+ 'exe': 't/stest',
+ 'parameters': None,
+ 'success': SUCCESS_STDERR,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1006,
+ 'test_class': FioExeTest,
+ 'exe': 't/strided.py',
+ 'parameters': ['{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1007,
+ 'test_class': FioExeTest,
+ 'exe': 't/zbd/run-tests-against-nullb',
+ 'parameters': ['-s', '1'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [Requirements.linux, Requirements.zbd,
+ Requirements.root],
+ },
+ {
+ 'test_id': 1008,
+ 'test_class': FioExeTest,
+ 'exe': 't/zbd/run-tests-against-nullb',
+ 'parameters': ['-s', '2'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [Requirements.linux, Requirements.zbd,
+ Requirements.root, Requirements.zoned_nullb],
+ },
+ {
+ 'test_id': 1009,
+ 'test_class': FioExeTest,
+ 'exe': 'unittests/unittest',
+ 'parameters': None,
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [Requirements.unittests],
+ },
+ {
+ 'test_id': 1010,
+ 'test_class': FioExeTest,
+ 'exe': 't/latency_percentiles.py',
+ 'parameters': ['-f', '{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
+ {
+ 'test_id': 1011,
+ 'test_class': FioExeTest,
+ 'exe': 't/jsonplus2csv_test.py',
+ 'parameters': ['-f', '{fio_path}'],
+ 'success': SUCCESS_DEFAULT,
+ 'requirements': [],
+ },
]
def parse_args():
+ """Parse command-line arguments."""
+
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--fio-root',
help='fio root path')
help='provide debug output')
parser.add_argument('-k', '--skip-req', action='store_true',
help='skip requirements checking')
+ parser.add_argument('-p', '--pass-through', action='append',
+ help='pass-through an argument to an executable test')
args = parser.parse_args()
return args
def main():
+ """Entry point."""
+
args = parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
+ pass_through = {}
+ if args.pass_through:
+ for arg in args.pass_through:
+ if not ':' in arg:
+ print("Invalid --pass-through argument '%s'" % arg)
+ print("Syntax for --pass-through is TESTNUMBER:ARGUMENT")
+ return
+ split = arg.split(":", 1)
+ pass_through[int(split[0])] = split[1]
+ logging.debug("Pass-through arguments: %s", pass_through)
+
if args.fio_root:
fio_root = args.fio_root
else:
fio_pre_job=fio_pre_job,
fio_pre_success=fio_pre_success,
output_format=output_format)
+ desc = config['job']
elif issubclass(config['test_class'], FioExeTest):
exe_path = os.path.join(fio_root, config['exe'])
if config['parameters']:
parameters = [p.format(fio_path=fio_path) for p in config['parameters']]
else:
- parameters = None
+ parameters = []
if Path(exe_path).suffix == '.py' and platform.system() == "Windows":
- if parameters:
- parameters.insert(0, exe_path)
- else:
- parameters = [exe_path]
+ parameters.insert(0, exe_path)
exe_path = "python.exe"
+ if config['test_id'] in pass_through:
+ parameters += pass_through[config['test_id']].split()
test = config['test_class'](exe_path, parameters,
config['success'])
+ desc = config['exe']
else:
print("Test {0} FAILED: unable to process test config".format(config['test_id']))
failed = failed + 1
continue
if not args.skip_req:
- skip = False
+ reqs_met = True
for req in config['requirements']:
- ok, reason = req()
- skip = not ok
- logging.debug("Test %d: Requirement '%s' met? %s" % (config['test_id'], reason, ok))
- if skip:
+ reqs_met, reason = req()
+ logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason,
+ reqs_met)
+ if not reqs_met:
break
- if skip:
- print("Test {0} SKIPPED ({1})".format(config['test_id'], reason))
+ if not reqs_met:
+ print("Test {0} SKIPPED ({1}) {2}".format(config['test_id'], reason, desc))
skipped = skipped + 1
continue
- test.setup(artifact_root, config['test_id'])
- test.run()
- test.check_result()
+ try:
+ test.setup(artifact_root, config['test_id'])
+ test.run()
+ test.check_result()
+ except KeyboardInterrupt:
+ break
+ except Exception as e:
+ test.passed = False
+ test.failure_reason += str(e)
+ logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc())
if test.passed:
result = "PASSED"
passed = passed + 1
else:
result = "FAILED: {0}".format(test.failure_reason)
failed = failed + 1
- with open(test.stderr_file, "r") as stderr_file:
- logging.debug("Test %d: stderr:\n%s" % (config['test_id'], stderr_file.read()))
- with open(test.stdout_file, "r") as stdout_file:
- logging.debug("Test %d: stdout:\n%s" % (config['test_id'], stdout_file.read()))
- print("Test {0} {1}".format(config['test_id'], result))
+ contents, _ = FioJobTest.get_file(test.stderr_file)
+ logging.debug("Test %d: stderr:\n%s", config['test_id'], contents)
+ contents, _ = FioJobTest.get_file(test.stdout_file)
+ logging.debug("Test %d: stdout:\n%s", config['test_id'], contents)
+ print("Test {0} {1} {2}".format(config['test_id'], result, desc))
print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))