1#! /usr/bin/python 2 3import os 4import sys 5import glob 6import optparse 7import tempfile 8import logging 9import shutil 10import ConfigParser 11 12class 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 19class Unsup(Exception): 20 def __init__(self, test): 21 self.test = test 22 def getMsg(self): 23 return '\'%s\'' % self.test.path 24 25class Event(dict): 26 terms = [ 27 'cpu', 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', 49 'watermark', 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): 72 log.debug(" Event %s" % name); 73 self.name = name; 74 self.group = '' 75 self.add(base) 76 self.add(data) 77 78 def compare_data(self, a, b): 79 # Allow multiple values in assignment separated by '|' 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 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])) 107 108 109# Test file description needs to have following sections: 110# [config] 111# - just single instance in file 112# - needs to specify: 113# 'command' - perf command name 114# 'args' - special command arguments 115# 'ret' - expected command return value (0 by default) 116# 117# [eventX:base] 118# - one or multiple instances in file 119# - expected values assignments 120class Test(object): 121 def __init__(self, path, options): 122 parser = ConfigParser.SafeConfigParser() 123 parser.read(path) 124 125 log.warning("running '%s'" % path) 126 127 self.path = path 128 self.test_dir = options.test_dir 129 self.perf = options.perf 130 self.command = parser.get('config', 'command') 131 self.args = parser.get('config', 'args') 132 133 try: 134 self.ret = parser.get('config', 'ret') 135 except: 136 self.ret = 0 137 138 self.expect = {} 139 self.result = {} 140 log.debug(" loading expected events"); 141 self.load_events(path, self.expect) 142 143 def is_event(self, name): 144 if name.find("event") == -1: 145 return False 146 else: 147 return True 148 149 def load_events(self, path, events): 150 parser_event = ConfigParser.SafeConfigParser() 151 parser_event.read(path) 152 153 # The event record section header contains 'event' word, 154 # optionaly followed by ':' allowing to load 'parent 155 # event' first as a base 156 for section in filter(self.is_event, parser_event.sections()): 157 158 parser_items = parser_event.items(section); 159 base_items = {} 160 161 # Read parent event if there's any 162 if (':' in section): 163 base = section[section.index(':') + 1:] 164 parser_base = ConfigParser.SafeConfigParser() 165 parser_base.read(self.test_dir + '/' + base) 166 base_items = parser_base.items('event') 167 168 e = Event(section, parser_items, base_items) 169 events[section] = e 170 171 def run_cmd(self, tempdir): 172 cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, 173 self.perf, self.command, tempdir, self.args) 174 ret = os.WEXITSTATUS(os.system(cmd)) 175 176 log.info(" '%s' ret %d " % (cmd, ret)) 177 178 if ret != int(self.ret): 179 raise Unsup(self) 180 181 def compare(self, expect, result): 182 match = {} 183 184 log.debug(" compare"); 185 186 # For each expected event find all matching 187 # events in result. Fail if there's not any. 188 for exp_name, exp_event in expect.items(): 189 exp_list = [] 190 log.debug(" matching [%s]" % exp_name) 191 for res_name, res_event in result.items(): 192 log.debug(" to [%s]" % res_name) 193 if (exp_event.equal(res_event)): 194 exp_list.append(res_name) 195 log.debug(" ->OK") 196 else: 197 log.debug(" ->FAIL"); 198 199 log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) 200 201 # we did not any matching event - fail 202 if (not exp_list): 203 exp_event.diff(res_event) 204 raise Fail(self, 'match failure'); 205 206 match[exp_name] = exp_list 207 208 # For each defined group in the expected events 209 # check we match the same group in the result. 210 for exp_name, exp_event in expect.items(): 211 group = exp_event.group 212 213 if (group == ''): 214 continue 215 216 for res_name in match[exp_name]: 217 res_group = result[res_name].group 218 if res_group not in match[group]: 219 raise Fail(self, 'group failure') 220 221 log.debug(" group: [%s] matches group leader %s" % 222 (exp_name, str(match[group]))) 223 224 log.debug(" matched") 225 226 def resolve_groups(self, events): 227 for name, event in events.items(): 228 group_fd = event['group_fd']; 229 if group_fd == '-1': 230 continue; 231 232 for iname, ievent in events.items(): 233 if (ievent['fd'] == group_fd): 234 event.group = iname 235 log.debug('[%s] has group leader [%s]' % (name, iname)) 236 break; 237 238 def run(self): 239 tempdir = tempfile.mkdtemp(); 240 241 try: 242 # run the test script 243 self.run_cmd(tempdir); 244 245 # load events expectation for the test 246 log.debug(" loading result events"); 247 for f in glob.glob(tempdir + '/event*'): 248 self.load_events(f, self.result); 249 250 # resolve group_fd to event names 251 self.resolve_groups(self.expect); 252 self.resolve_groups(self.result); 253 254 # do the expectation - results matching - both ways 255 self.compare(self.expect, self.result) 256 self.compare(self.result, self.expect) 257 258 finally: 259 # cleanup 260 shutil.rmtree(tempdir) 261 262 263def run_tests(options): 264 for f in glob.glob(options.test_dir + '/' + options.test): 265 try: 266 Test(f, options).run() 267 except Unsup, obj: 268 log.warning("unsupp %s" % obj.getMsg()) 269 270def setup_log(verbose): 271 global log 272 level = logging.CRITICAL 273 274 if verbose == 1: 275 level = logging.WARNING 276 if verbose == 2: 277 level = logging.INFO 278 if verbose >= 3: 279 level = logging.DEBUG 280 281 log = logging.getLogger('test') 282 log.setLevel(level) 283 ch = logging.StreamHandler() 284 ch.setLevel(level) 285 formatter = logging.Formatter('%(message)s') 286 ch.setFormatter(formatter) 287 log.addHandler(ch) 288 289USAGE = '''%s [OPTIONS] 290 -d dir # tests dir 291 -p path # perf binary 292 -t test # single test 293 -v # verbose level 294''' % sys.argv[0] 295 296def main(): 297 parser = optparse.OptionParser(usage=USAGE) 298 299 parser.add_option("-t", "--test", 300 action="store", type="string", dest="test") 301 parser.add_option("-d", "--test-dir", 302 action="store", type="string", dest="test_dir") 303 parser.add_option("-p", "--perf", 304 action="store", type="string", dest="perf") 305 parser.add_option("-v", "--verbose", 306 action="count", dest="verbose") 307 308 options, args = parser.parse_args() 309 if args: 310 parser.error('FAILED wrong arguments %s' % ' '.join(args)) 311 return -1 312 313 setup_log(options.verbose) 314 315 if not options.test_dir: 316 print 'FAILED no -d option specified' 317 sys.exit(-1) 318 319 if not options.test: 320 options.test = 'test*' 321 322 try: 323 run_tests(options) 324 325 except Fail, obj: 326 print "FAILED %s" % obj.getMsg(); 327 sys.exit(-1) 328 329 sys.exit(0) 330 331if __name__ == '__main__': 332 main() 333