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