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# 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 119class Test(object): 120 def __init__(self, path, options): 121 parser = ConfigParser.SafeConfigParser() 122 parser.read(path) 123 124 log.warning("running '%s'" % path) 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 = {} 139 log.debug(" loading expected events"); 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 152 # The event record section header contains 'event' word, 153 # optionaly followed by ':' allowing to load 'parent 154 # event' first as a base 155 for section in filter(self.is_event, parser_event.sections()): 156 157 parser_items = parser_event.items(section); 158 base_items = {} 159 160 # Read parent event if there's any 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 175 log.info(" '%s' ret %d " % (cmd, ret)) 176 177 if ret != int(self.ret): 178 raise Unsup(self) 179 180 def compare(self, expect, result): 181 match = {} 182 183 log.debug(" compare"); 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 198 log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) 199 200 # we did not any matching event - fail 201 if (not exp_list): 202 exp_event.diff(res_event) 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 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 220 log.debug(" group: [%s] matches group leader %s" % 221 (exp_name, str(match[group]))) 222 223 log.debug(" matched") 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 240 try: 241 # run the test script 242 self.run_cmd(tempdir); 243 244 # load events expectation for the test 245 log.debug(" loading result events"); 246 for f in glob.glob(tempdir + '/event*'): 247 self.load_events(f, self.result); 248 249 # resolve group_fd to event names 250 self.resolve_groups(self.expect); 251 self.resolve_groups(self.result); 252 253 # do the expectation - results matching - both ways 254 self.compare(self.expect, self.result) 255 self.compare(self.result, self.expect) 256 257 finally: 258 # cleanup 259 shutil.rmtree(tempdir) 260 261 262def 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 269def 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 288USAGE = '''%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 295def 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 330if __name__ == '__main__': 331 main() 332