]>
Commit | Line | Data |
---|---|---|
52502bf2 | 1 | #! /usr/bin/python |
b2441318 | 2 | # SPDX-License-Identifier: GPL-2.0 |
52502bf2 JO |
3 | |
4 | import os | |
5 | import sys | |
6 | import glob | |
7 | import optparse | |
8 | import tempfile | |
9 | import logging | |
10 | import shutil | |
11 | import ConfigParser | |
12 | ||
dde622a5 | 13 | def data_equal(a, b): |
04c31bcf JO |
14 | # Allow multiple values in assignment separated by '|' |
15 | a_list = a.split('|') | |
16 | b_list = b.split('|') | |
17 | ||
18 | for a_item in a_list: | |
19 | for b_item in b_list: | |
20 | if (a_item == b_item): | |
21 | return True | |
22 | elif (a_item == '*') or (b_item == '*'): | |
23 | return True | |
24 | ||
25 | return False | |
26 | ||
52502bf2 JO |
27 | class Fail(Exception): |
28 | def __init__(self, test, msg): | |
29 | self.msg = msg | |
30 | self.test = test | |
31 | def getMsg(self): | |
32 | return '\'%s\' - %s' % (self.test.path, self.msg) | |
33 | ||
19508c04 TR |
34 | class Notest(Exception): |
35 | def __init__(self, test, arch): | |
36 | self.arch = arch | |
37 | self.test = test | |
38 | def getMsg(self): | |
39 | return '[%s] \'%s\'' % (self.arch, self.test.path) | |
40 | ||
52502bf2 JO |
41 | class Unsup(Exception): |
42 | def __init__(self, test): | |
43 | self.test = test | |
44 | def getMsg(self): | |
45 | return '\'%s\'' % self.test.path | |
46 | ||
47 | class Event(dict): | |
48 | terms = [ | |
c21d0030 | 49 | 'cpu', |
52502bf2 JO |
50 | 'flags', |
51 | 'type', | |
52 | 'size', | |
53 | 'config', | |
54 | 'sample_period', | |
55 | 'sample_type', | |
56 | 'read_format', | |
57 | 'disabled', | |
58 | 'inherit', | |
59 | 'pinned', | |
60 | 'exclusive', | |
61 | 'exclude_user', | |
62 | 'exclude_kernel', | |
63 | 'exclude_hv', | |
64 | 'exclude_idle', | |
65 | 'mmap', | |
66 | 'comm', | |
67 | 'freq', | |
68 | 'inherit_stat', | |
69 | 'enable_on_exec', | |
70 | 'task', | |
45e4089b | 71 | 'watermark', |
52502bf2 JO |
72 | 'precise_ip', |
73 | 'mmap_data', | |
74 | 'sample_id_all', | |
75 | 'exclude_host', | |
76 | 'exclude_guest', | |
77 | 'exclude_callchain_kernel', | |
78 | 'exclude_callchain_user', | |
79 | 'wakeup_events', | |
80 | 'bp_type', | |
81 | 'config1', | |
82 | 'config2', | |
83 | 'branch_sample_type', | |
84 | 'sample_regs_user', | |
85 | 'sample_stack_user', | |
86 | ] | |
87 | ||
88 | def add(self, data): | |
89 | for key, val in data: | |
90 | log.debug(" %s = %s" % (key, val)) | |
91 | self[key] = val | |
92 | ||
93 | def __init__(self, name, data, base): | |
ce90e385 | 94 | log.debug(" Event %s" % name); |
52502bf2 JO |
95 | self.name = name; |
96 | self.group = '' | |
97 | self.add(base) | |
98 | self.add(data) | |
99 | ||
52502bf2 JO |
100 | def equal(self, other): |
101 | for t in Event.terms: | |
102 | log.debug(" [%s] %s %s" % (t, self[t], other[t])); | |
103 | if not self.has_key(t) or not other.has_key(t): | |
104 | return False | |
dde622a5 | 105 | if not data_equal(self[t], other[t]): |
52502bf2 JO |
106 | return False |
107 | return True | |
108 | ||
1f41873c JO |
109 | def optional(self): |
110 | if self.has_key('optional') and self['optional'] == '1': | |
111 | return True | |
112 | return False | |
113 | ||
ce90e385 ACM |
114 | def diff(self, other): |
115 | for t in Event.terms: | |
116 | if not self.has_key(t) or not other.has_key(t): | |
117 | continue | |
dde622a5 | 118 | if not data_equal(self[t], other[t]): |
ce90e385 | 119 | log.warning("expected %s=%s, got %s" % (t, self[t], other[t])) |
ce90e385 | 120 | |
8dfec403 JO |
121 | # Test file description needs to have following sections: |
122 | # [config] | |
123 | # - just single instance in file | |
124 | # - needs to specify: | |
125 | # 'command' - perf command name | |
126 | # 'args' - special command arguments | |
127 | # 'ret' - expected command return value (0 by default) | |
19508c04 TR |
128 | # 'arch' - architecture specific test (optional) |
129 | # comma separated list, ! at the beginning | |
130 | # negates it. | |
8dfec403 JO |
131 | # |
132 | # [eventX:base] | |
133 | # - one or multiple instances in file | |
134 | # - expected values assignments | |
52502bf2 JO |
135 | class Test(object): |
136 | def __init__(self, path, options): | |
137 | parser = ConfigParser.SafeConfigParser() | |
138 | parser.read(path) | |
139 | ||
097c8758 | 140 | log.warning("running '%s'" % path) |
52502bf2 JO |
141 | |
142 | self.path = path | |
143 | self.test_dir = options.test_dir | |
144 | self.perf = options.perf | |
145 | self.command = parser.get('config', 'command') | |
146 | self.args = parser.get('config', 'args') | |
147 | ||
148 | try: | |
149 | self.ret = parser.get('config', 'ret') | |
150 | except: | |
151 | self.ret = 0 | |
152 | ||
19508c04 TR |
153 | try: |
154 | self.arch = parser.get('config', 'arch') | |
155 | log.warning("test limitation '%s'" % self.arch) | |
156 | except: | |
157 | self.arch = '' | |
158 | ||
52502bf2 JO |
159 | self.expect = {} |
160 | self.result = {} | |
ce90e385 | 161 | log.debug(" loading expected events"); |
52502bf2 JO |
162 | self.load_events(path, self.expect) |
163 | ||
164 | def is_event(self, name): | |
165 | if name.find("event") == -1: | |
166 | return False | |
167 | else: | |
168 | return True | |
169 | ||
19508c04 TR |
170 | def skip_test(self, myarch): |
171 | # If architecture not set always run test | |
172 | if self.arch == '': | |
173 | # log.warning("test for arch %s is ok" % myarch) | |
174 | return False | |
175 | ||
176 | # Allow multiple values in assignment separated by ',' | |
177 | arch_list = self.arch.split(',') | |
178 | ||
179 | # Handle negated list such as !s390x,ppc | |
180 | if arch_list[0][0] == '!': | |
181 | arch_list[0] = arch_list[0][1:] | |
182 | log.warning("excluded architecture list %s" % arch_list) | |
183 | for arch_item in arch_list: | |
184 | # log.warning("test for %s arch is %s" % (arch_item, myarch)) | |
185 | if arch_item == myarch: | |
186 | return True | |
187 | return False | |
188 | ||
189 | for arch_item in arch_list: | |
190 | # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch)) | |
191 | if arch_item == myarch: | |
192 | return False | |
193 | return True | |
194 | ||
52502bf2 JO |
195 | def load_events(self, path, events): |
196 | parser_event = ConfigParser.SafeConfigParser() | |
197 | parser_event.read(path) | |
198 | ||
8dfec403 JO |
199 | # The event record section header contains 'event' word, |
200 | # optionaly followed by ':' allowing to load 'parent | |
201 | # event' first as a base | |
52502bf2 JO |
202 | for section in filter(self.is_event, parser_event.sections()): |
203 | ||
204 | parser_items = parser_event.items(section); | |
205 | base_items = {} | |
206 | ||
8dfec403 | 207 | # Read parent event if there's any |
52502bf2 JO |
208 | if (':' in section): |
209 | base = section[section.index(':') + 1:] | |
210 | parser_base = ConfigParser.SafeConfigParser() | |
211 | parser_base.read(self.test_dir + '/' + base) | |
212 | base_items = parser_base.items('event') | |
213 | ||
214 | e = Event(section, parser_items, base_items) | |
215 | events[section] = e | |
216 | ||
217 | def run_cmd(self, tempdir): | |
19508c04 TR |
218 | junk1, junk2, junk3, junk4, myarch = (os.uname()) |
219 | ||
220 | if self.skip_test(myarch): | |
221 | raise Notest(self, myarch) | |
222 | ||
52502bf2 JO |
223 | cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, |
224 | self.perf, self.command, tempdir, self.args) | |
225 | ret = os.WEXITSTATUS(os.system(cmd)) | |
226 | ||
04c31bcf | 227 | log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret))) |
52502bf2 | 228 | |
dde622a5 | 229 | if not data_equal(str(ret), str(self.ret)): |
52502bf2 JO |
230 | raise Unsup(self) |
231 | ||
232 | def compare(self, expect, result): | |
233 | match = {} | |
234 | ||
ce90e385 | 235 | log.debug(" compare"); |
52502bf2 JO |
236 | |
237 | # For each expected event find all matching | |
238 | # events in result. Fail if there's not any. | |
239 | for exp_name, exp_event in expect.items(): | |
240 | exp_list = [] | |
241 | log.debug(" matching [%s]" % exp_name) | |
242 | for res_name, res_event in result.items(): | |
243 | log.debug(" to [%s]" % res_name) | |
244 | if (exp_event.equal(res_event)): | |
245 | exp_list.append(res_name) | |
246 | log.debug(" ->OK") | |
247 | else: | |
248 | log.debug(" ->FAIL"); | |
249 | ||
ce90e385 | 250 | log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) |
52502bf2 JO |
251 | |
252 | # we did not any matching event - fail | |
1f41873c JO |
253 | if not exp_list: |
254 | if exp_event.optional(): | |
255 | log.debug(" %s does not match, but is optional" % exp_name) | |
256 | else: | |
257 | exp_event.diff(res_event) | |
258 | raise Fail(self, 'match failure'); | |
52502bf2 JO |
259 | |
260 | match[exp_name] = exp_list | |
261 | ||
262 | # For each defined group in the expected events | |
263 | # check we match the same group in the result. | |
264 | for exp_name, exp_event in expect.items(): | |
265 | group = exp_event.group | |
266 | ||
267 | if (group == ''): | |
268 | continue | |
269 | ||
52502bf2 JO |
270 | for res_name in match[exp_name]: |
271 | res_group = result[res_name].group | |
272 | if res_group not in match[group]: | |
273 | raise Fail(self, 'group failure') | |
274 | ||
ce90e385 | 275 | log.debug(" group: [%s] matches group leader %s" % |
52502bf2 JO |
276 | (exp_name, str(match[group]))) |
277 | ||
ce90e385 | 278 | log.debug(" matched") |
52502bf2 JO |
279 | |
280 | def resolve_groups(self, events): | |
281 | for name, event in events.items(): | |
282 | group_fd = event['group_fd']; | |
283 | if group_fd == '-1': | |
284 | continue; | |
285 | ||
286 | for iname, ievent in events.items(): | |
287 | if (ievent['fd'] == group_fd): | |
288 | event.group = iname | |
289 | log.debug('[%s] has group leader [%s]' % (name, iname)) | |
290 | break; | |
291 | ||
292 | def run(self): | |
293 | tempdir = tempfile.mkdtemp(); | |
294 | ||
d4fcf0a8 JO |
295 | try: |
296 | # run the test script | |
297 | self.run_cmd(tempdir); | |
52502bf2 | 298 | |
d4fcf0a8 | 299 | # load events expectation for the test |
ce90e385 | 300 | log.debug(" loading result events"); |
d4fcf0a8 JO |
301 | for f in glob.glob(tempdir + '/event*'): |
302 | self.load_events(f, self.result); | |
52502bf2 | 303 | |
d4fcf0a8 JO |
304 | # resolve group_fd to event names |
305 | self.resolve_groups(self.expect); | |
306 | self.resolve_groups(self.result); | |
52502bf2 | 307 | |
d4fcf0a8 JO |
308 | # do the expectation - results matching - both ways |
309 | self.compare(self.expect, self.result) | |
310 | self.compare(self.result, self.expect) | |
52502bf2 | 311 | |
d4fcf0a8 JO |
312 | finally: |
313 | # cleanup | |
314 | shutil.rmtree(tempdir) | |
52502bf2 JO |
315 | |
316 | ||
317 | def run_tests(options): | |
318 | for f in glob.glob(options.test_dir + '/' + options.test): | |
319 | try: | |
320 | Test(f, options).run() | |
321 | except Unsup, obj: | |
322 | log.warning("unsupp %s" % obj.getMsg()) | |
19508c04 TR |
323 | except Notest, obj: |
324 | log.warning("skipped %s" % obj.getMsg()) | |
52502bf2 JO |
325 | |
326 | def setup_log(verbose): | |
327 | global log | |
328 | level = logging.CRITICAL | |
329 | ||
330 | if verbose == 1: | |
331 | level = logging.WARNING | |
332 | if verbose == 2: | |
333 | level = logging.INFO | |
334 | if verbose >= 3: | |
335 | level = logging.DEBUG | |
336 | ||
337 | log = logging.getLogger('test') | |
338 | log.setLevel(level) | |
339 | ch = logging.StreamHandler() | |
340 | ch.setLevel(level) | |
341 | formatter = logging.Formatter('%(message)s') | |
342 | ch.setFormatter(formatter) | |
343 | log.addHandler(ch) | |
344 | ||
345 | USAGE = '''%s [OPTIONS] | |
346 | -d dir # tests dir | |
347 | -p path # perf binary | |
348 | -t test # single test | |
349 | -v # verbose level | |
350 | ''' % sys.argv[0] | |
351 | ||
352 | def main(): | |
353 | parser = optparse.OptionParser(usage=USAGE) | |
354 | ||
355 | parser.add_option("-t", "--test", | |
356 | action="store", type="string", dest="test") | |
357 | parser.add_option("-d", "--test-dir", | |
358 | action="store", type="string", dest="test_dir") | |
359 | parser.add_option("-p", "--perf", | |
360 | action="store", type="string", dest="perf") | |
361 | parser.add_option("-v", "--verbose", | |
362 | action="count", dest="verbose") | |
363 | ||
364 | options, args = parser.parse_args() | |
365 | if args: | |
366 | parser.error('FAILED wrong arguments %s' % ' '.join(args)) | |
367 | return -1 | |
368 | ||
369 | setup_log(options.verbose) | |
370 | ||
371 | if not options.test_dir: | |
372 | print 'FAILED no -d option specified' | |
373 | sys.exit(-1) | |
374 | ||
375 | if not options.test: | |
376 | options.test = 'test*' | |
377 | ||
378 | try: | |
379 | run_tests(options) | |
380 | ||
381 | except Fail, obj: | |
382 | print "FAILED %s" % obj.getMsg(); | |
383 | sys.exit(-1) | |
384 | ||
385 | sys.exit(0) | |
386 | ||
387 | if __name__ == '__main__': | |
388 | main() |