t/readonly: adapt to use fiotestlib
[fio.git] / t / random_seed.py
1 #!/usr/bin/env python3
2 """
3 # random_seed.py
4 #
5 # Test fio's random seed options.
6 #
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
10 #
11 # USAGE
12 # see python3 random_seed.py --help
13 #
14 # EXAMPLES
15 # python3 t/random_seed.py
16 # python3 t/random_seed.py -f ./fio
17 #
18 # REQUIREMENTS
19 # Python 3.6
20 #
21 """
22 import os
23 import sys
24 import time
25 import locale
26 import logging
27 import argparse
28 from pathlib import Path
29 from fiotestlib import FioJobCmdTest, run_fio_tests
30
31 class FioRandTest(FioJobCmdTest):
32     """fio random seed test."""
33
34     def setup(self, parameters):
35         """Setup the test."""
36
37         fio_args = [
38             "--debug=random",
39             "--name=random_seed",
40             "--ioengine=null",
41             "--filesize=32k",
42             "--rw=randread",
43             f"--output={self.filenames['output']}",
44         ]
45         for opt in ['randseed', 'randrepeat', 'allrandrepeat']:
46             if opt in self.fio_opts:
47                 option = f"--{opt}={self.fio_opts[opt]}"
48                 fio_args.append(option)
49
50         super().setup(fio_args)
51
52     def get_rand_seeds(self):
53         """Collect random seeds from --debug=random output."""
54         with open(self.filenames['output'], "r",
55                   encoding=locale.getpreferredencoding()) as out_file:
56             file_data = out_file.read()
57
58             offsets = 0
59             for line in file_data.split('\n'):
60                 if 'random' in line and 'FIO_RAND_NR_OFFS=' in line:
61                     tokens = line.split('=')
62                     offsets = int(tokens[len(tokens)-1])
63                     break
64
65             if offsets == 0:
66                 pass
67                 # find an exception to throw
68
69             seed_list = []
70             for line in file_data.split('\n'):
71                 if 'random' not in line:
72                     continue
73                 if 'rand_seeds[' in line:
74                     tokens = line.split('=')
75                     seed = int(tokens[-1])
76                     seed_list.append(seed)
77                     # assume that seeds are in order
78
79             return seed_list
80
81
82 class TestRR(FioRandTest):
83     """
84     Test object for [all]randrepeat. If run for the first time just collect the
85     seeds. For later runs make sure the seeds match or do not match those
86     previously collected.
87     """
88     # one set of seeds is for randrepeat=0 and the other is for randrepeat=1
89     seeds = { 0: None, 1: None }
90
91     def check_result(self):
92         """Check output for allrandrepeat=1."""
93
94         opt = 'randrepeat' if 'randrepeat' in self.fio_opts else 'allrandrepeat'
95         rr = self.fio_opts[opt]
96         rand_seeds = self.get_rand_seeds()
97
98         if not TestRR.seeds[rr]:
99             TestRR.seeds[rr] = rand_seeds
100             logging.debug("TestRR: saving rand_seeds for [a]rr=%d", rr)
101         else:
102             if rr:
103                 if TestRR.seeds[1] != rand_seeds:
104                     self.passed = False
105                     print(f"TestRR: unexpected seed mismatch for [a]rr={rr}")
106                 else:
107                     logging.debug("TestRR: seeds correctly match for [a]rr=%d", rr)
108                 if TestRR.seeds[0] == rand_seeds:
109                     self.passed = False
110                     print("TestRR: seeds unexpectedly match those from system RNG")
111             else:
112                 if TestRR.seeds[0] == rand_seeds:
113                     self.passed = False
114                     print(f"TestRR: unexpected seed match for [a]rr={rr}")
115                 else:
116                     logging.debug("TestRR: seeds correctly don't match for [a]rr=%d", rr)
117                 if TestRR.seeds[1] == rand_seeds:
118                     self.passed = False
119                     print("TestRR: random seeds unexpectedly match those from [a]rr=1")
120
121
122 class TestRS(FioRandTest):
123     """
124     Test object when randseed=something controls the generated seeds. If run
125     for the first time for a given randseed just collect the seeds. For later
126     runs with the same seed make sure the seeds are the same as those
127     previously collected.
128     """
129     seeds = {}
130
131     def check_result(self):
132         """Check output for randseed=something."""
133
134         rand_seeds = self.get_rand_seeds()
135         randseed = self.fio_opts['randseed']
136
137         logging.debug("randseed = %s", randseed)
138
139         if randseed not in TestRS.seeds:
140             TestRS.seeds[randseed] = rand_seeds
141             logging.debug("TestRS: saving rand_seeds")
142         else:
143             if TestRS.seeds[randseed] != rand_seeds:
144                 self.passed = False
145                 print("TestRS: seeds don't match when they should")
146             else:
147                 logging.debug("TestRS: seeds correctly match")
148
149         # Now try to find seeds generated using a different randseed and make
150         # sure they *don't* match
151         for key, value in TestRS.seeds.items():
152             if key != randseed:
153                 if value == rand_seeds:
154                     self.passed = False
155                     print("TestRS: randseeds differ but generated seeds match.")
156                 else:
157                     logging.debug("TestRS: randseeds differ and generated seeds also differ.")
158
159
160 def parse_args():
161     """Parse command-line arguments."""
162
163     parser = argparse.ArgumentParser()
164     parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
165     parser.add_argument('-a', '--artifact-root', help='artifact root directory')
166     parser.add_argument('-d', '--debug', help='enable debug output', action='store_true')
167     parser.add_argument('-s', '--skip', nargs='+', type=int,
168                         help='list of test(s) to skip')
169     parser.add_argument('-o', '--run-only', nargs='+', type=int,
170                         help='list of test(s) to run, skipping all others')
171     args = parser.parse_args()
172
173     return args
174
175
176 def main():
177     """Run tests of fio random seed options"""
178
179     args = parse_args()
180
181     if args.debug:
182         logging.basicConfig(level=logging.DEBUG)
183     else:
184         logging.basicConfig(level=logging.INFO)
185
186     artifact_root = args.artifact_root if args.artifact_root else \
187         f"random-seed-test-{time.strftime('%Y%m%d-%H%M%S')}"
188     os.mkdir(artifact_root)
189     print(f"Artifact directory is {artifact_root}")
190
191     if args.fio:
192         fio_path = str(Path(args.fio).absolute())
193     else:
194         fio_path = 'fio'
195     print(f"fio path is {fio_path}")
196
197     test_list = [
198         {
199             "test_id": 1,
200             "fio_opts": {
201                 "randrepeat": 0,
202                 },
203             "test_class": TestRR,
204         },
205         {
206             "test_id": 2,
207             "fio_opts": {
208                 "randrepeat": 0,
209                 },
210             "test_class": TestRR,
211         },
212         {
213             "test_id": 3,
214             "fio_opts": {
215                 "randrepeat": 1,
216                 },
217             "test_class": TestRR,
218         },
219         {
220             "test_id": 4,
221             "fio_opts": {
222                 "randrepeat": 1,
223                 },
224             "test_class": TestRR,
225         },
226         {
227             "test_id": 5,
228             "fio_opts": {
229                 "allrandrepeat": 0,
230                 },
231             "test_class": TestRR,
232         },
233         {
234             "test_id": 6,
235             "fio_opts": {
236                 "allrandrepeat": 0,
237                 },
238             "test_class": TestRR,
239         },
240         {
241             "test_id": 7,
242             "fio_opts": {
243                 "allrandrepeat": 1,
244                 },
245             "test_class": TestRR,
246         },
247         {
248             "test_id": 8,
249             "fio_opts": {
250                 "allrandrepeat": 1,
251                 },
252             "test_class": TestRR,
253         },
254         {
255             "test_id": 9,
256             "fio_opts": {
257                 "randrepeat": 0,
258                 "randseed": "12345",
259                 },
260             "test_class": TestRS,
261         },
262         {
263             "test_id": 10,
264             "fio_opts": {
265                 "randrepeat": 0,
266                 "randseed": "12345",
267                 },
268             "test_class": TestRS,
269         },
270         {
271             "test_id": 11,
272             "fio_opts": {
273                 "randrepeat": 1,
274                 "randseed": "12345",
275                 },
276             "test_class": TestRS,
277         },
278         {
279             "test_id": 12,
280             "fio_opts": {
281                 "allrandrepeat": 0,
282                 "randseed": "12345",
283                 },
284             "test_class": TestRS,
285         },
286         {
287             "test_id": 13,
288             "fio_opts": {
289                 "allrandrepeat": 1,
290                 "randseed": "12345",
291                 },
292             "test_class": TestRS,
293         },
294         {
295             "test_id": 14,
296             "fio_opts": {
297                 "randrepeat": 0,
298                 "randseed": "67890",
299                 },
300             "test_class": TestRS,
301         },
302         {
303             "test_id": 15,
304             "fio_opts": {
305                 "randrepeat": 1,
306                 "randseed": "67890",
307                 },
308             "test_class": TestRS,
309         },
310         {
311             "test_id": 16,
312             "fio_opts": {
313                 "allrandrepeat": 0,
314                 "randseed": "67890",
315                 },
316             "test_class": TestRS,
317         },
318         {
319             "test_id": 17,
320             "fio_opts": {
321                 "allrandrepeat": 1,
322                 "randseed": "67890",
323                 },
324             "test_class": TestRS,
325         },
326     ]
327
328     test_env = {
329               'fio_path': fio_path,
330               'fio_root': str(Path(__file__).absolute().parent.parent),
331               'artifact_root': artifact_root,
332               'basename': 'random',
333               }
334
335     _, failed, _ = run_fio_tests(test_list, test_env, args)
336     sys.exit(failed)
337
338
339 if __name__ == '__main__':
340     main()