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