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