1#! /usr/bin/python 2# SPDX-License-Identifier: GPL-2.0 3 4import os 5import sys 6import glob 7import optparse 8import tempfile 9import logging 10import shutil 11import ConfigParser 12 13def data_equal(a, b): 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 27class 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 34class 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 41class Unsup(Exception): 42 def __init__(self, test): 43 self.test = test 44 def getMsg(self): 45 return '\'%s\'' % self.test.path 46 47class Event(dict): 48 terms = [ 49 'cpu', 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', 71 'watermark', 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): 94 log.debug(" Event %s" % name); 95 self.name = name; 96 self.group = '' 97 self.add(base) 98 self.add(data) 99 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 105 if not data_equal(self[t], other[t]): 106 return False 107 return True 108 109 def optional(self): 110 if self.has_key('optional') and self['optional'] == '1': 111 return True 112 return False 113 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 118 if not data_equal(self[t], other[t]): 119 log.warning("expected %s=%s, got %s" % (t, self[t], other[t])) 120 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) 128# 'arch' - architecture specific test (optional) 129# comma separated list, ! at the beginning 130# negates it. 131# 132# [eventX:base] 133# - one or multiple instances in file 134# - expected values assignments 135class Test(object): 136 def __init__(self, path, options): 137 parser = ConfigParser.SafeConfigParser() 138 parser.read(path) 139 140 log.warning("running '%s'" % path) 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 153 try: 154 self.arch = parser.get('config', 'arch') 155 log.warning("test limitation '%s'" % self.arch) 156 except: 157 self.arch = '' 158 159 self.expect = {} 160 self.result = {} 161 log.debug(" loading expected events"); 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 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 195 def load_events(self, path, events): 196 parser_event = ConfigParser.SafeConfigParser() 197 parser_event.read(path) 198 199 # The event record section header contains 'event' word, 200 # optionaly followed by ':' allowing to load 'parent 201 # event' first as a base 202 for section in filter(self.is_event, parser_event.sections()): 203 204 parser_items = parser_event.items(section); 205 base_items = {} 206 207 # Read parent event if there's any 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): 218 junk1, junk2, junk3, junk4, myarch = (os.uname()) 219 220 if self.skip_test(myarch): 221 raise Notest(self, myarch) 222 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 227 log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret))) 228 229 if not data_equal(str(ret), str(self.ret)): 230 raise Unsup(self) 231 232 def compare(self, expect, result): 233 match = {} 234 235 log.debug(" compare"); 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 res_event = {} 242 log.debug(" matching [%s]" % exp_name) 243 for res_name, res_event in result.items(): 244 log.debug(" to [%s]" % res_name) 245 if (exp_event.equal(res_event)): 246 exp_list.append(res_name) 247 log.debug(" ->OK") 248 else: 249 log.debug(" ->FAIL"); 250 251 log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) 252 253 # we did not any matching event - fail 254 if not exp_list: 255 if exp_event.optional(): 256 log.debug(" %s does not match, but is optional" % exp_name) 257 else: 258 if not res_event: 259 log.debug(" res_event is empty"); 260 else: 261 exp_event.diff(res_event) 262 raise Fail(self, 'match failure'); 263 264 match[exp_name] = exp_list 265 266 # For each defined group in the expected events 267 # check we match the same group in the result. 268 for exp_name, exp_event in expect.items(): 269 group = exp_event.group 270 271 if (group == ''): 272 continue 273 274 for res_name in match[exp_name]: 275 res_group = result[res_name].group 276 if res_group not in match[group]: 277 raise Fail(self, 'group failure') 278 279 log.debug(" group: [%s] matches group leader %s" % 280 (exp_name, str(match[group]))) 281 282 log.debug(" matched") 283 284 def resolve_groups(self, events): 285 for name, event in events.items(): 286 group_fd = event['group_fd']; 287 if group_fd == '-1': 288 continue; 289 290 for iname, ievent in events.items(): 291 if (ievent['fd'] == group_fd): 292 event.group = iname 293 log.debug('[%s] has group leader [%s]' % (name, iname)) 294 break; 295 296 def run(self): 297 tempdir = tempfile.mkdtemp(); 298 299 try: 300 # run the test script 301 self.run_cmd(tempdir); 302 303 # load events expectation for the test 304 log.debug(" loading result events"); 305 for f in glob.glob(tempdir + '/event*'): 306 self.load_events(f, self.result); 307 308 # resolve group_fd to event names 309 self.resolve_groups(self.expect); 310 self.resolve_groups(self.result); 311 312 # do the expectation - results matching - both ways 313 self.compare(self.expect, self.result) 314 self.compare(self.result, self.expect) 315 316 finally: 317 # cleanup 318 shutil.rmtree(tempdir) 319 320 321def run_tests(options): 322 for f in glob.glob(options.test_dir + '/' + options.test): 323 try: 324 Test(f, options).run() 325 except Unsup, obj: 326 log.warning("unsupp %s" % obj.getMsg()) 327 except Notest, obj: 328 log.warning("skipped %s" % obj.getMsg()) 329 330def setup_log(verbose): 331 global log 332 level = logging.CRITICAL 333 334 if verbose == 1: 335 level = logging.WARNING 336 if verbose == 2: 337 level = logging.INFO 338 if verbose >= 3: 339 level = logging.DEBUG 340 341 log = logging.getLogger('test') 342 log.setLevel(level) 343 ch = logging.StreamHandler() 344 ch.setLevel(level) 345 formatter = logging.Formatter('%(message)s') 346 ch.setFormatter(formatter) 347 log.addHandler(ch) 348 349USAGE = '''%s [OPTIONS] 350 -d dir # tests dir 351 -p path # perf binary 352 -t test # single test 353 -v # verbose level 354''' % sys.argv[0] 355 356def main(): 357 parser = optparse.OptionParser(usage=USAGE) 358 359 parser.add_option("-t", "--test", 360 action="store", type="string", dest="test") 361 parser.add_option("-d", "--test-dir", 362 action="store", type="string", dest="test_dir") 363 parser.add_option("-p", "--perf", 364 action="store", type="string", dest="perf") 365 parser.add_option("-v", "--verbose", 366 action="count", dest="verbose") 367 368 options, args = parser.parse_args() 369 if args: 370 parser.error('FAILED wrong arguments %s' % ' '.join(args)) 371 return -1 372 373 setup_log(options.verbose) 374 375 if not options.test_dir: 376 print 'FAILED no -d option specified' 377 sys.exit(-1) 378 379 if not options.test: 380 options.test = 'test*' 381 382 try: 383 run_tests(options) 384 385 except Fail, obj: 386 print "FAILED %s" % obj.getMsg(); 387 sys.exit(-1) 388 389 sys.exit(0) 390 391if __name__ == '__main__': 392 main() 393