5 # Test fio's random seed options.
7 # - make sure that randseed overrides randrepeat and allrandrepeat
8 # - make sure that seeds differ across invocations when [all]randrepeat=0 and randseed is not set
9 # - make sure that seeds are always the same when [all]randrepeat=1 and randseed is not set
12 # see python3 random_seed.py --help
15 # python3 t/random_seed.py
16 # python3 t/random_seed.py -f ./fio
28 from pathlib import Path
31 """fio random seed test."""
33 def __init__(self, artifact_root, test_options, debug):
35 artifact_root root directory for artifacts (subdirectory will be created under here)
36 test test specification
38 self.artifact_root = artifact_root
39 self.test_options = test_options
41 self.filename_stub = None
44 self.test_dir = os.path.abspath(os.path.join(self.artifact_root,
45 f"{self.test_options['test_id']:03d}"))
46 if not os.path.exists(self.test_dir):
47 os.mkdir(self.test_dir)
49 self.filename_stub = f"random{self.test_options['test_id']:03d}"
50 self.filenames['command'] = os.path.join(self.test_dir, f"{self.filename_stub}.command")
51 self.filenames['stdout'] = os.path.join(self.test_dir, f"{self.filename_stub}.stdout")
52 self.filenames['stderr'] = os.path.join(self.test_dir, f"{self.filename_stub}.stderr")
53 self.filenames['exitcode'] = os.path.join(self.test_dir, f"{self.filename_stub}.exitcode")
54 self.filenames['output'] = os.path.join(self.test_dir, f"{self.filename_stub}.output")
56 def run_fio(self, fio_path):
65 f"--output={self.filenames['output']}",
67 for opt in ['randseed', 'randrepeat', 'allrandrepeat']:
68 if opt in self.test_options:
69 option = f"--{opt}={self.test_options[opt]}"
70 fio_args.append(option)
72 command = [fio_path] + fio_args
73 with open(self.filenames['command'], "w+", encoding=locale.getpreferredencoding()) as command_file:
74 command_file.write(" ".join(command))
79 with open(self.filenames['stdout'], "w+", encoding=locale.getpreferredencoding()) as stdout_file, \
80 open(self.filenames['stderr'], "w+", encoding=locale.getpreferredencoding()) as stderr_file, \
81 open(self.filenames['exitcode'], "w+", encoding=locale.getpreferredencoding()) as exitcode_file:
83 # Avoid using subprocess.run() here because when a timeout occurs,
84 # fio will be stopped with SIGKILL. This does not give fio a
85 # chance to clean up and means that child processes may continue
86 # running and submitting IO.
87 proc = subprocess.Popen(command,
91 universal_newlines=True)
92 proc.communicate(timeout=300)
93 exitcode_file.write(f'{proc.returncode}\n')
94 passed &= (proc.returncode == 0)
95 except subprocess.TimeoutExpired:
99 print("Timeout expired")
106 print(f"Exception: {sys.exc_info()}")
111 def get_rand_seeds(self):
112 """Collect random seeds from --debug=random output."""
113 with open(self.filenames['output'], "r", encoding=locale.getpreferredencoding()) as out_file:
114 file_data = out_file.read()
117 for line in file_data.split('\n'):
118 if 'random' in line and 'FIO_RAND_NR_OFFS=' in line:
119 tokens = line.split('=')
120 offsets = int(tokens[len(tokens)-1])
125 # find an exception to throw
128 for line in file_data.split('\n'):
129 if 'random' not in line:
131 if 'rand_seeds[' in line:
132 tokens = line.split('=')
133 seed = int(tokens[-1])
134 seed_list.append(seed)
135 # assume that seeds are in order
140 """Check test output."""
142 raise NotImplementedError()
145 class TestRR(FioRandTest):
147 Test object for [all]randrepeat. If run for the first time just collect the
148 seeds. For later runs make sure the seeds match or do not match those
149 previously collected.
151 # one set of seeds is for randrepeat=0 and the other is for randrepeat=1
152 seeds = { 0: None, 1: None }
155 """Check output for allrandrepeat=1."""
158 opt = 'randrepeat' if 'randrepeat' in self.test_options else 'allrandrepeat'
159 rr = self.test_options[opt]
160 rand_seeds = self.get_rand_seeds()
162 if not TestRR.seeds[rr]:
163 TestRR.seeds[rr] = rand_seeds
165 print(f"TestRR: saving rand_seeds for [a]rr={rr}")
168 if TestRR.seeds[1] != rand_seeds:
170 print(f"TestRR: unexpected seed mismatch for [a]rr={rr}")
173 print(f"TestRR: seeds correctly match for [a]rr={rr}")
174 if TestRR.seeds[0] == rand_seeds:
176 print("TestRR: seeds unexpectedly match those from system RNG")
178 if TestRR.seeds[0] == rand_seeds:
180 print(f"TestRR: unexpected seed match for [a]rr={rr}")
183 print(f"TestRR: seeds correctly don't match for [a]rr={rr}")
184 if TestRR.seeds[1] == rand_seeds:
186 print(f"TestRR: random seeds unexpectedly match those from [a]rr=1")
191 class TestRS(FioRandTest):
193 Test object when randseed=something controls the generated seeds. If run
194 for the first time for a given randseed just collect the seeds. For later
195 runs with the same seed make sure the seeds are the same as those
196 previously collected.
201 """Check output for randseed=something."""
204 rand_seeds = self.get_rand_seeds()
205 randseed = self.test_options['randseed']
208 print("randseed = ", randseed)
210 if randseed not in TestRS.seeds:
211 TestRS.seeds[randseed] = rand_seeds
213 print("TestRS: saving rand_seeds")
215 if TestRS.seeds[randseed] != rand_seeds:
217 print("TestRS: seeds don't match when they should")
220 print("TestRS: seeds correctly match")
222 # Now try to find seeds generated using a different randseed and make
223 # sure they *don't* match
224 for key in TestRS.seeds:
226 if TestRS.seeds[key] == rand_seeds:
228 print("TestRS: randseeds differ but generated seeds match.")
231 print("TestRS: randseeds differ and generated seeds also differ.")
237 """Parse command-line arguments."""
239 parser = argparse.ArgumentParser()
240 parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
241 parser.add_argument('-a', '--artifact-root', help='artifact root directory')
242 parser.add_argument('-d', '--debug', help='enable debug output', action='store_true')
243 parser.add_argument('-s', '--skip', nargs='+', type=int,
244 help='list of test(s) to skip')
245 parser.add_argument('-o', '--run-only', nargs='+', type=int,
246 help='list of test(s) to run, skipping all others')
247 args = parser.parse_args()
253 """Run tests of fio random seed options"""
257 artifact_root = args.artifact_root if args.artifact_root else \
258 f"random-seed-test-{time.strftime('%Y%m%d-%H%M%S')}"
259 os.mkdir(artifact_root)
260 print(f"Artifact directory is {artifact_root}")
263 fio = str(Path(args.fio).absolute())
266 print(f"fio path is {fio}")
369 for test in test_list:
370 if (args.skip and test['test_id'] in args.skip) or \
371 (args.run_only and test['test_id'] not in args.run_only):
372 skipped = skipped + 1
373 outcome = 'SKIPPED (User request)'
375 test_obj = test['test_obj'](artifact_root, test, args.debug)
376 status = test_obj.run_fio(fio)
378 status = test_obj.check()
386 print(f"**********Test {test['test_id']} {outcome}**********")
388 print(f"{passed} tests passed, {failed} failed, {skipped} skipped")
393 if __name__ == '__main__':