1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Lifrom __future__ import absolute_import 3*9c5db199SXin Lifrom __future__ import division 4*9c5db199SXin Lifrom __future__ import print_function 5*9c5db199SXin Li 6*9c5db199SXin Liimport json 7*9c5db199SXin Liimport math 8*9c5db199SXin Liimport os 9*9c5db199SXin Liimport re 10*9c5db199SXin Li 11*9c5db199SXin Liimport common 12*9c5db199SXin Lifrom autotest_lib.tko import models 13*9c5db199SXin Lifrom autotest_lib.tko import status_lib 14*9c5db199SXin Lifrom autotest_lib.tko import utils as tko_utils 15*9c5db199SXin Lifrom autotest_lib.tko.parsers import base 16*9c5db199SXin Lifrom autotest_lib.tko.parsers import version_0 17*9c5db199SXin Lifrom six.moves import map 18*9c5db199SXin Lifrom six.moves import range 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Liclass job(version_0.job): 22*9c5db199SXin Li """Represents a job.""" 23*9c5db199SXin Li 24*9c5db199SXin Li def exit_status(self): 25*9c5db199SXin Li """Returns the string exit status of this job.""" 26*9c5db199SXin Li 27*9c5db199SXin Li # Find the .autoserv_execute path. 28*9c5db199SXin Li top_dir = tko_utils.find_toplevel_job_dir(self.dir) 29*9c5db199SXin Li if not top_dir: 30*9c5db199SXin Li return 'ABORT' 31*9c5db199SXin Li execute_path = os.path.join(top_dir, '.autoserv_execute') 32*9c5db199SXin Li 33*9c5db199SXin Li # If for some reason we can't read the status code, assume disaster. 34*9c5db199SXin Li if not os.path.exists(execute_path): 35*9c5db199SXin Li return 'ABORT' 36*9c5db199SXin Li lines = open(execute_path).readlines() 37*9c5db199SXin Li if len(lines) < 2: 38*9c5db199SXin Li return 'ABORT' 39*9c5db199SXin Li try: 40*9c5db199SXin Li status_code = int(lines[1]) 41*9c5db199SXin Li except ValueError: 42*9c5db199SXin Li return 'ABORT' 43*9c5db199SXin Li 44*9c5db199SXin Li if not os.WIFEXITED(status_code): 45*9c5db199SXin Li # Looks like a signal - an ABORT. 46*9c5db199SXin Li return 'ABORT' 47*9c5db199SXin Li elif os.WEXITSTATUS(status_code) != 0: 48*9c5db199SXin Li # Looks like a non-zero exit - a failure. 49*9c5db199SXin Li return 'FAIL' 50*9c5db199SXin Li else: 51*9c5db199SXin Li # Looks like exit code == 0. 52*9c5db199SXin Li return 'GOOD' 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Liclass kernel(models.kernel): 56*9c5db199SXin Li """Represents a kernel.""" 57*9c5db199SXin Li 58*9c5db199SXin Li def __init__(self, base, patches): 59*9c5db199SXin Li if base: 60*9c5db199SXin Li patches = [patch(*p.split()) for p in patches] 61*9c5db199SXin Li hashes = [p.hash for p in patches] 62*9c5db199SXin Li kernel_hash = self.compute_hash(base, hashes) 63*9c5db199SXin Li else: 64*9c5db199SXin Li base = 'UNKNOWN' 65*9c5db199SXin Li patches = [] 66*9c5db199SXin Li kernel_hash = 'UNKNOWN' 67*9c5db199SXin Li super(kernel, self).__init__(base, patches, kernel_hash) 68*9c5db199SXin Li 69*9c5db199SXin Li 70*9c5db199SXin Liclass test(models.test): 71*9c5db199SXin Li """Represents a test.""" 72*9c5db199SXin Li 73*9c5db199SXin Li @staticmethod 74*9c5db199SXin Li def load_iterations(keyval_path): 75*9c5db199SXin Li return iteration.load_from_keyval(keyval_path) 76*9c5db199SXin Li 77*9c5db199SXin Li 78*9c5db199SXin Liclass iteration(models.iteration): 79*9c5db199SXin Li """Represents an iteration.""" 80*9c5db199SXin Li 81*9c5db199SXin Li @staticmethod 82*9c5db199SXin Li def parse_line_into_dicts(line, attr_dict, perf_dict): 83*9c5db199SXin Li key, val_type, value = "", "", "" 84*9c5db199SXin Li 85*9c5db199SXin Li # Figure out what the key, value and keyval type are. 86*9c5db199SXin Li typed_match = re.search('^([^=]*)\{(\w*)\}=(.*)$', line) 87*9c5db199SXin Li if typed_match: 88*9c5db199SXin Li key, val_type, value = typed_match.groups() 89*9c5db199SXin Li else: 90*9c5db199SXin Li # Old-fashioned untyped match, assume perf. 91*9c5db199SXin Li untyped_match = re.search('^([^=]*)=(.*)$', line) 92*9c5db199SXin Li if untyped_match: 93*9c5db199SXin Li key, value = untyped_match.groups() 94*9c5db199SXin Li val_type = 'perf' 95*9c5db199SXin Li 96*9c5db199SXin Li # Parse the actual value into a dict. 97*9c5db199SXin Li try: 98*9c5db199SXin Li if val_type == 'attr': 99*9c5db199SXin Li attr_dict[key] = value 100*9c5db199SXin Li elif val_type == 'perf': 101*9c5db199SXin Li # first check if value is in the form of 'mean+-deviation' 102*9c5db199SXin Li if isinstance(value, str): 103*9c5db199SXin Li r = re.compile('(\d+.?\d*)\+-(\d+.?\d*)') 104*9c5db199SXin Li match = r.match(value) 105*9c5db199SXin Li if match: 106*9c5db199SXin Li perf_dict[key] = float(match.group(1)) 107*9c5db199SXin Li perf_dict['%s_dev' % key] = float(match.group(2)) 108*9c5db199SXin Li return 109*9c5db199SXin Li # otherwise try to interpret as a regular float 110*9c5db199SXin Li perf_dict[key] = float(value) 111*9c5db199SXin Li else: 112*9c5db199SXin Li raise ValueError 113*9c5db199SXin Li except ValueError: 114*9c5db199SXin Li msg = ('WARNING: line "%s" found in test ' 115*9c5db199SXin Li 'iteration keyval could not be parsed') 116*9c5db199SXin Li msg %= line 117*9c5db199SXin Li tko_utils.dprint(msg) 118*9c5db199SXin Li 119*9c5db199SXin Li 120*9c5db199SXin Liclass perf_value_iteration(models.perf_value_iteration): 121*9c5db199SXin Li """Represents a perf value iteration.""" 122*9c5db199SXin Li 123*9c5db199SXin Li @staticmethod 124*9c5db199SXin Li def parse_line_into_dict(line): 125*9c5db199SXin Li """ 126*9c5db199SXin Li Parse a perf measurement text line into a dictionary. 127*9c5db199SXin Li 128*9c5db199SXin Li The line is assumed to be a JSON-formatted string containing key/value 129*9c5db199SXin Li pairs, where each pair represents a piece of information associated 130*9c5db199SXin Li with a measured perf metric: 131*9c5db199SXin Li 132*9c5db199SXin Li 'description': a string description for the perf metric. 133*9c5db199SXin Li 'value': a numeric value, or list of numeric values. 134*9c5db199SXin Li 'units': the string units associated with the perf metric. 135*9c5db199SXin Li 'higher_is_better': a boolean whether a higher value is considered 136*9c5db199SXin Li better. If False, a lower value is considered better. 137*9c5db199SXin Li 'graph': a string indicating the name of the perf dashboard graph 138*9c5db199SXin Li on which the perf data will be displayed. 139*9c5db199SXin Li 140*9c5db199SXin Li The resulting dictionary will also have a standard deviation key/value 141*9c5db199SXin Li pair, 'stddev'. If the perf measurement value is a list of values 142*9c5db199SXin Li instead of a single value, then the average and standard deviation of 143*9c5db199SXin Li the list of values is computed and stored. If a single value, the 144*9c5db199SXin Li value itself is used, and is associated with a standard deviation of 0. 145*9c5db199SXin Li 146*9c5db199SXin Li @param line: A string line of JSON text from a perf measurements output 147*9c5db199SXin Li file. 148*9c5db199SXin Li 149*9c5db199SXin Li @return A dictionary containing the parsed perf measurement information 150*9c5db199SXin Li along with a computed standard deviation value (key 'stddev'), or 151*9c5db199SXin Li an empty dictionary if the inputted line cannot be parsed. 152*9c5db199SXin Li """ 153*9c5db199SXin Li try: 154*9c5db199SXin Li perf_dict = json.loads(line) 155*9c5db199SXin Li except ValueError: 156*9c5db199SXin Li msg = 'Could not parse perf measurements line as json: "%s"' % line 157*9c5db199SXin Li tko_utils.dprint(msg) 158*9c5db199SXin Li return {} 159*9c5db199SXin Li 160*9c5db199SXin Li def mean_and_standard_deviation(data): 161*9c5db199SXin Li """ 162*9c5db199SXin Li Computes the mean and standard deviation of a list of numbers. 163*9c5db199SXin Li 164*9c5db199SXin Li @param data: A list of numbers. 165*9c5db199SXin Li 166*9c5db199SXin Li @return A 2-tuple (mean, standard_deviation) computed from the list 167*9c5db199SXin Li of numbers. 168*9c5db199SXin Li 169*9c5db199SXin Li """ 170*9c5db199SXin Li n = len(data) 171*9c5db199SXin Li if n == 0: 172*9c5db199SXin Li return 0.0, 0.0 173*9c5db199SXin Li if n == 1: 174*9c5db199SXin Li return data[0], 0.0 175*9c5db199SXin Li mean = float(sum(data)) / n 176*9c5db199SXin Li # Divide by n-1 to compute "sample standard deviation". 177*9c5db199SXin Li variance = sum([(elem - mean) ** 2 for elem in data]) / (n - 1) 178*9c5db199SXin Li return mean, math.sqrt(variance) 179*9c5db199SXin Li 180*9c5db199SXin Li value = perf_dict['value'] 181*9c5db199SXin Li perf_dict['stddev'] = 0.0 182*9c5db199SXin Li if isinstance(value, list): 183*9c5db199SXin Li # list wrapping the map IS needed here. 184*9c5db199SXin Li value, stddev = mean_and_standard_deviation(list(map(float, 185*9c5db199SXin Li value))) 186*9c5db199SXin Li perf_dict['value'] = value 187*9c5db199SXin Li perf_dict['stddev'] = stddev 188*9c5db199SXin Li 189*9c5db199SXin Li return perf_dict 190*9c5db199SXin Li 191*9c5db199SXin Li 192*9c5db199SXin Liclass status_line(version_0.status_line): 193*9c5db199SXin Li """Represents a status line.""" 194*9c5db199SXin Li 195*9c5db199SXin Li def __init__(self, indent, status, subdir, testname, reason, 196*9c5db199SXin Li optional_fields): 197*9c5db199SXin Li # Handle INFO fields. 198*9c5db199SXin Li if status == 'INFO': 199*9c5db199SXin Li self.type = 'INFO' 200*9c5db199SXin Li self.indent = indent 201*9c5db199SXin Li self.status = self.subdir = self.testname = self.reason = None 202*9c5db199SXin Li self.optional_fields = optional_fields 203*9c5db199SXin Li else: 204*9c5db199SXin Li # Everything else is backwards compatible. 205*9c5db199SXin Li super(status_line, self).__init__(indent, status, subdir, 206*9c5db199SXin Li testname, reason, 207*9c5db199SXin Li optional_fields) 208*9c5db199SXin Li 209*9c5db199SXin Li 210*9c5db199SXin Li def is_successful_reboot(self, current_status): 211*9c5db199SXin Li """ 212*9c5db199SXin Li Checks whether the status represents a successful reboot. 213*9c5db199SXin Li 214*9c5db199SXin Li @param current_status: A string representing the current status. 215*9c5db199SXin Li 216*9c5db199SXin Li @return True, if the status represents a successful reboot, or False 217*9c5db199SXin Li if not. 218*9c5db199SXin Li 219*9c5db199SXin Li """ 220*9c5db199SXin Li # Make sure this is a reboot line. 221*9c5db199SXin Li if self.testname != 'reboot': 222*9c5db199SXin Li return False 223*9c5db199SXin Li 224*9c5db199SXin Li # Make sure this was not a failure. 225*9c5db199SXin Li if status_lib.is_worse_than_or_equal_to(current_status, 'FAIL'): 226*9c5db199SXin Li return False 227*9c5db199SXin Li 228*9c5db199SXin Li # It must have been a successful reboot. 229*9c5db199SXin Li return True 230*9c5db199SXin Li 231*9c5db199SXin Li 232*9c5db199SXin Li def get_kernel(self): 233*9c5db199SXin Li # Get the base kernel version. 234*9c5db199SXin Li fields = self.optional_fields 235*9c5db199SXin Li base = re.sub('-autotest$', '', fields.get('kernel', '')) 236*9c5db199SXin Li # Get a list of patches. 237*9c5db199SXin Li patches = [] 238*9c5db199SXin Li patch_index = 0 239*9c5db199SXin Li while ('patch%d' % patch_index) in fields: 240*9c5db199SXin Li patches.append(fields['patch%d' % patch_index]) 241*9c5db199SXin Li patch_index += 1 242*9c5db199SXin Li # Create a new kernel instance. 243*9c5db199SXin Li return kernel(base, patches) 244*9c5db199SXin Li 245*9c5db199SXin Li 246*9c5db199SXin Li def get_timestamp(self): 247*9c5db199SXin Li return tko_utils.get_timestamp(self.optional_fields, 'timestamp') 248*9c5db199SXin Li 249*9c5db199SXin Li 250*9c5db199SXin Li# The default implementations from version 0 will do for now. 251*9c5db199SXin Lipatch = version_0.patch 252*9c5db199SXin Li 253*9c5db199SXin Li 254*9c5db199SXin Liclass parser(base.parser): 255*9c5db199SXin Li """Represents a parser.""" 256*9c5db199SXin Li 257*9c5db199SXin Li @staticmethod 258*9c5db199SXin Li def make_job(dir): 259*9c5db199SXin Li return job(dir) 260*9c5db199SXin Li 261*9c5db199SXin Li 262*9c5db199SXin Li @staticmethod 263*9c5db199SXin Li def make_stub_abort(indent, subdir, testname, timestamp, reason): 264*9c5db199SXin Li """ 265*9c5db199SXin Li Creates an abort string. 266*9c5db199SXin Li 267*9c5db199SXin Li @param indent: The number of indentation levels for the string. 268*9c5db199SXin Li @param subdir: The subdirectory name. 269*9c5db199SXin Li @param testname: The test name. 270*9c5db199SXin Li @param timestamp: The timestamp value. 271*9c5db199SXin Li @param reason: The reason string. 272*9c5db199SXin Li 273*9c5db199SXin Li @return A string describing the abort. 274*9c5db199SXin Li 275*9c5db199SXin Li """ 276*9c5db199SXin Li indent = '\t' * indent 277*9c5db199SXin Li if not subdir: 278*9c5db199SXin Li subdir = '----' 279*9c5db199SXin Li if not testname: 280*9c5db199SXin Li testname = '----' 281*9c5db199SXin Li 282*9c5db199SXin Li # There is no guarantee that this will be set. 283*9c5db199SXin Li timestamp_field = '' 284*9c5db199SXin Li if timestamp: 285*9c5db199SXin Li timestamp_field = '\ttimestamp=%s' % timestamp 286*9c5db199SXin Li 287*9c5db199SXin Li msg = indent + 'END ABORT\t%s\t%s%s\t%s' 288*9c5db199SXin Li return msg % (subdir, testname, timestamp_field, reason) 289*9c5db199SXin Li 290*9c5db199SXin Li 291*9c5db199SXin Li @staticmethod 292*9c5db199SXin Li def put_back_line_and_abort( 293*9c5db199SXin Li line_buffer, line, indent, subdir, testname, timestamp, reason): 294*9c5db199SXin Li """ 295*9c5db199SXin Li Appends a line to the line buffer and aborts. 296*9c5db199SXin Li 297*9c5db199SXin Li @param line_buffer: A line_buffer object. 298*9c5db199SXin Li @param line: A line to append to the line buffer. 299*9c5db199SXin Li @param indent: The number of indentation levels. 300*9c5db199SXin Li @param subdir: The subdirectory name. 301*9c5db199SXin Li @param testname: The test name. 302*9c5db199SXin Li @param timestamp: The timestamp value. 303*9c5db199SXin Li @param reason: The reason string. 304*9c5db199SXin Li 305*9c5db199SXin Li """ 306*9c5db199SXin Li tko_utils.dprint('Unexpected indent: aborting log parse') 307*9c5db199SXin Li line_buffer.put_back(line) 308*9c5db199SXin Li abort = parser.make_stub_abort( 309*9c5db199SXin Li indent, subdir, testname, timestamp, reason) 310*9c5db199SXin Li line_buffer.put_back(abort) 311*9c5db199SXin Li 312*9c5db199SXin Li 313*9c5db199SXin Li def state_iterator(self, buffer): 314*9c5db199SXin Li """ 315*9c5db199SXin Li Yields a list of tests out of the buffer. 316*9c5db199SXin Li 317*9c5db199SXin Li @param buffer: a buffer object 318*9c5db199SXin Li 319*9c5db199SXin Li """ 320*9c5db199SXin Li line = None 321*9c5db199SXin Li new_tests = [] 322*9c5db199SXin Li job_count, boot_count = 0, 0 323*9c5db199SXin Li min_stack_size = 0 324*9c5db199SXin Li stack = status_lib.status_stack() 325*9c5db199SXin Li current_kernel = kernel("", []) # UNKNOWN 326*9c5db199SXin Li current_status = status_lib.statuses[-1] 327*9c5db199SXin Li current_reason = None 328*9c5db199SXin Li started_time_stack = [None] 329*9c5db199SXin Li subdir_stack = [None] 330*9c5db199SXin Li testname_stack = [None] 331*9c5db199SXin Li running_client = None 332*9c5db199SXin Li running_test = None 333*9c5db199SXin Li running_reasons = set() 334*9c5db199SXin Li ignored_lines = [] 335*9c5db199SXin Li yield [] # We're ready to start running. 336*9c5db199SXin Li 337*9c5db199SXin Li def print_ignored_lines(): 338*9c5db199SXin Li """ 339*9c5db199SXin Li Prints the ignored_lines using tko_utils.dprint method. 340*9c5db199SXin Li """ 341*9c5db199SXin Li num_lines = len(ignored_lines) 342*9c5db199SXin Li if num_lines > 2: 343*9c5db199SXin Li tko_utils.dprint('The following %s lines were ignored:' % 344*9c5db199SXin Li num_lines) 345*9c5db199SXin Li tko_utils.dprint('%r' % ignored_lines[0]) 346*9c5db199SXin Li tko_utils.dprint('...') 347*9c5db199SXin Li tko_utils.dprint('%r' % ignored_lines[-1]) 348*9c5db199SXin Li elif num_lines == 2: 349*9c5db199SXin Li tko_utils.dprint('The following %s lines were ignored:' % 350*9c5db199SXin Li num_lines) 351*9c5db199SXin Li tko_utils.dprint('%r' % ignored_lines[0]) 352*9c5db199SXin Li tko_utils.dprint('%r' % ignored_lines[-1]) 353*9c5db199SXin Li elif num_lines == 1: 354*9c5db199SXin Li tko_utils.dprint('The following line was ignored:') 355*9c5db199SXin Li tko_utils.dprint('%r' % ignored_lines[0]) 356*9c5db199SXin Li 357*9c5db199SXin Li # Create a RUNNING SERVER_JOB entry to represent the entire test. 358*9c5db199SXin Li running_job = test.parse_partial_test(self.job, '----', 'SERVER_JOB', 359*9c5db199SXin Li '', current_kernel, 360*9c5db199SXin Li self.job.started_time) 361*9c5db199SXin Li new_tests.append(running_job) 362*9c5db199SXin Li 363*9c5db199SXin Li while True: 364*9c5db199SXin Li # Are we finished with parsing? 365*9c5db199SXin Li if buffer.size() == 0 and self.finished: 366*9c5db199SXin Li if ignored_lines: 367*9c5db199SXin Li print_ignored_lines() 368*9c5db199SXin Li ignored_lines = [] 369*9c5db199SXin Li if stack.size() == 0: 370*9c5db199SXin Li break 371*9c5db199SXin Li # We have status lines left on the stack; 372*9c5db199SXin Li # we need to implicitly abort them first. 373*9c5db199SXin Li tko_utils.dprint('\nUnexpected end of job, aborting') 374*9c5db199SXin Li abort_subdir_stack = list(subdir_stack) 375*9c5db199SXin Li if self.job.aborted_by: 376*9c5db199SXin Li reason = 'Job aborted by %s' % self.job.aborted_by 377*9c5db199SXin Li reason += self.job.aborted_on.strftime( 378*9c5db199SXin Li ' at %b %d %H:%M:%S') 379*9c5db199SXin Li else: 380*9c5db199SXin Li reason = 'Job aborted unexpectedly' 381*9c5db199SXin Li 382*9c5db199SXin Li timestamp = line.optional_fields.get('timestamp') 383*9c5db199SXin Li for i in reversed(range(stack.size())): 384*9c5db199SXin Li if abort_subdir_stack: 385*9c5db199SXin Li subdir = abort_subdir_stack.pop() 386*9c5db199SXin Li else: 387*9c5db199SXin Li subdir = None 388*9c5db199SXin Li abort = self.make_stub_abort( 389*9c5db199SXin Li i, subdir, subdir, timestamp, reason) 390*9c5db199SXin Li buffer.put(abort) 391*9c5db199SXin Li 392*9c5db199SXin Li # Stop processing once the buffer is empty. 393*9c5db199SXin Li if buffer.size() == 0: 394*9c5db199SXin Li yield new_tests 395*9c5db199SXin Li new_tests = [] 396*9c5db199SXin Li continue 397*9c5db199SXin Li 398*9c5db199SXin Li # Reinitialize the per-iteration state. 399*9c5db199SXin Li started_time = None 400*9c5db199SXin Li finished_time = None 401*9c5db199SXin Li 402*9c5db199SXin Li # Get the next line. 403*9c5db199SXin Li raw_line = status_lib.clean_raw_line(buffer.get()) 404*9c5db199SXin Li line = status_line.parse_line(raw_line) 405*9c5db199SXin Li if line is None: 406*9c5db199SXin Li ignored_lines.append(raw_line) 407*9c5db199SXin Li continue 408*9c5db199SXin Li elif ignored_lines: 409*9c5db199SXin Li print_ignored_lines() 410*9c5db199SXin Li ignored_lines = [] 411*9c5db199SXin Li 412*9c5db199SXin Li # Do an initial check of the indentation. 413*9c5db199SXin Li expected_indent = stack.size() 414*9c5db199SXin Li if line.type == 'END': 415*9c5db199SXin Li expected_indent -= 1 416*9c5db199SXin Li if line.indent < expected_indent: 417*9c5db199SXin Li # ABORT the current level if indentation was unexpectedly low. 418*9c5db199SXin Li self.put_back_line_and_abort( 419*9c5db199SXin Li buffer, raw_line, stack.size() - 1, subdir_stack[-1], 420*9c5db199SXin Li testname_stack[-1], line.optional_fields.get('timestamp'), 421*9c5db199SXin Li line.reason) 422*9c5db199SXin Li continue 423*9c5db199SXin Li elif line.indent > expected_indent: 424*9c5db199SXin Li # Ignore the log if the indent was unexpectedly high. 425*9c5db199SXin Li tko_utils.dprint('ignoring line because of extra indentation') 426*9c5db199SXin Li continue 427*9c5db199SXin Li 428*9c5db199SXin Li # Initial line processing. 429*9c5db199SXin Li if line.type == 'START': 430*9c5db199SXin Li stack.start() 431*9c5db199SXin Li started_time = line.get_timestamp() 432*9c5db199SXin Li testname = None 433*9c5db199SXin Li if (line.testname is None and line.subdir is None 434*9c5db199SXin Li and not running_test): 435*9c5db199SXin Li # We just started a client; all tests are relative to here. 436*9c5db199SXin Li min_stack_size = stack.size() 437*9c5db199SXin Li # Start a "RUNNING" CLIENT_JOB entry. 438*9c5db199SXin Li job_name = 'CLIENT_JOB.%d' % job_count 439*9c5db199SXin Li running_client = test.parse_partial_test(self.job, None, 440*9c5db199SXin Li job_name, 441*9c5db199SXin Li '', current_kernel, 442*9c5db199SXin Li started_time) 443*9c5db199SXin Li msg = 'RUNNING: %s\n%s\n' 444*9c5db199SXin Li msg %= (running_client.status, running_client.testname) 445*9c5db199SXin Li tko_utils.dprint(msg) 446*9c5db199SXin Li new_tests.append(running_client) 447*9c5db199SXin Li testname = running_client.testname 448*9c5db199SXin Li elif stack.size() == min_stack_size + 1 and not running_test: 449*9c5db199SXin Li # We just started a new test; insert a running record. 450*9c5db199SXin Li running_reasons = set() 451*9c5db199SXin Li if line.reason: 452*9c5db199SXin Li running_reasons.add(line.reason) 453*9c5db199SXin Li running_test = test.parse_partial_test(self.job, 454*9c5db199SXin Li line.subdir, 455*9c5db199SXin Li line.testname, 456*9c5db199SXin Li line.reason, 457*9c5db199SXin Li current_kernel, 458*9c5db199SXin Li started_time) 459*9c5db199SXin Li msg = 'RUNNING: %s\nSubdir: %s\nTestname: %s\n%s' 460*9c5db199SXin Li msg %= (running_test.status, running_test.subdir, 461*9c5db199SXin Li running_test.testname, running_test.reason) 462*9c5db199SXin Li tko_utils.dprint(msg) 463*9c5db199SXin Li new_tests.append(running_test) 464*9c5db199SXin Li testname = running_test.testname 465*9c5db199SXin Li started_time_stack.append(started_time) 466*9c5db199SXin Li subdir_stack.append(line.subdir) 467*9c5db199SXin Li testname_stack.append(testname) 468*9c5db199SXin Li continue 469*9c5db199SXin Li elif line.type == 'INFO': 470*9c5db199SXin Li fields = line.optional_fields 471*9c5db199SXin Li # Update the current kernel if one is defined in the info. 472*9c5db199SXin Li if 'kernel' in fields: 473*9c5db199SXin Li current_kernel = line.get_kernel() 474*9c5db199SXin Li # Update the SERVER_JOB reason if one was logged for an abort. 475*9c5db199SXin Li if 'job_abort_reason' in fields: 476*9c5db199SXin Li running_job.reason = fields['job_abort_reason'] 477*9c5db199SXin Li new_tests.append(running_job) 478*9c5db199SXin Li continue 479*9c5db199SXin Li elif line.type == 'STATUS': 480*9c5db199SXin Li # Update the stacks. 481*9c5db199SXin Li if line.subdir and stack.size() > min_stack_size: 482*9c5db199SXin Li subdir_stack[-1] = line.subdir 483*9c5db199SXin Li testname_stack[-1] = line.testname 484*9c5db199SXin Li # Update the status, start and finished times. 485*9c5db199SXin Li stack.update(line.status) 486*9c5db199SXin Li if status_lib.is_worse_than_or_equal_to(line.status, 487*9c5db199SXin Li current_status): 488*9c5db199SXin Li if line.reason: 489*9c5db199SXin Li # Update the status of a currently running test. 490*9c5db199SXin Li if running_test: 491*9c5db199SXin Li running_reasons.add(line.reason) 492*9c5db199SXin Li running_reasons = tko_utils.drop_redundant_messages( 493*9c5db199SXin Li running_reasons) 494*9c5db199SXin Li sorted_reasons = sorted(running_reasons) 495*9c5db199SXin Li running_test.reason = ', '.join(sorted_reasons) 496*9c5db199SXin Li current_reason = running_test.reason 497*9c5db199SXin Li new_tests.append(running_test) 498*9c5db199SXin Li msg = 'update RUNNING reason: %s' % line.reason 499*9c5db199SXin Li tko_utils.dprint(msg) 500*9c5db199SXin Li else: 501*9c5db199SXin Li current_reason = line.reason 502*9c5db199SXin Li current_status = stack.current_status() 503*9c5db199SXin Li started_time = None 504*9c5db199SXin Li finished_time = line.get_timestamp() 505*9c5db199SXin Li # If this is a non-test entry there's nothing else to do. 506*9c5db199SXin Li if line.testname is None and line.subdir is None: 507*9c5db199SXin Li continue 508*9c5db199SXin Li elif line.type == 'END': 509*9c5db199SXin Li # Grab the current subdir off of the subdir stack, or, if this 510*9c5db199SXin Li # is the end of a job, just pop it off. 511*9c5db199SXin Li if (line.testname is None and line.subdir is None 512*9c5db199SXin Li and not running_test): 513*9c5db199SXin Li min_stack_size = stack.size() - 1 514*9c5db199SXin Li subdir_stack.pop() 515*9c5db199SXin Li testname_stack.pop() 516*9c5db199SXin Li else: 517*9c5db199SXin Li line.subdir = subdir_stack.pop() 518*9c5db199SXin Li testname_stack.pop() 519*9c5db199SXin Li if not subdir_stack[-1] and stack.size() > min_stack_size: 520*9c5db199SXin Li subdir_stack[-1] = line.subdir 521*9c5db199SXin Li # Update the status, start and finished times. 522*9c5db199SXin Li stack.update(line.status) 523*9c5db199SXin Li current_status = stack.end() 524*9c5db199SXin Li if stack.size() > min_stack_size: 525*9c5db199SXin Li stack.update(current_status) 526*9c5db199SXin Li current_status = stack.current_status() 527*9c5db199SXin Li started_time = started_time_stack.pop() 528*9c5db199SXin Li finished_time = line.get_timestamp() 529*9c5db199SXin Li # Update the current kernel. 530*9c5db199SXin Li if line.is_successful_reboot(current_status): 531*9c5db199SXin Li current_kernel = line.get_kernel() 532*9c5db199SXin Li # Adjust the testname if this is a reboot. 533*9c5db199SXin Li if line.testname == 'reboot' and line.subdir is None: 534*9c5db199SXin Li line.testname = 'boot.%d' % boot_count 535*9c5db199SXin Li else: 536*9c5db199SXin Li assert False 537*9c5db199SXin Li 538*9c5db199SXin Li # Have we just finished a test? 539*9c5db199SXin Li if stack.size() <= min_stack_size: 540*9c5db199SXin Li # If there was no testname, just use the subdir. 541*9c5db199SXin Li if line.testname is None: 542*9c5db199SXin Li line.testname = line.subdir 543*9c5db199SXin Li # If there was no testname or subdir, use 'CLIENT_JOB'. 544*9c5db199SXin Li if line.testname is None: 545*9c5db199SXin Li line.testname = 'CLIENT_JOB.%d' % job_count 546*9c5db199SXin Li running_test = running_client 547*9c5db199SXin Li job_count += 1 548*9c5db199SXin Li if not status_lib.is_worse_than_or_equal_to( 549*9c5db199SXin Li current_status, 'ABORT'): 550*9c5db199SXin Li # A job hasn't really failed just because some of the 551*9c5db199SXin Li # tests it ran have. 552*9c5db199SXin Li current_status = 'GOOD' 553*9c5db199SXin Li 554*9c5db199SXin Li if not current_reason: 555*9c5db199SXin Li current_reason = line.reason 556*9c5db199SXin Li new_test = test.parse_test(self.job, 557*9c5db199SXin Li line.subdir, 558*9c5db199SXin Li line.testname, 559*9c5db199SXin Li current_status, 560*9c5db199SXin Li current_reason, 561*9c5db199SXin Li current_kernel, 562*9c5db199SXin Li started_time, 563*9c5db199SXin Li finished_time, 564*9c5db199SXin Li running_test) 565*9c5db199SXin Li running_test = None 566*9c5db199SXin Li current_status = status_lib.statuses[-1] 567*9c5db199SXin Li current_reason = None 568*9c5db199SXin Li if new_test.testname == ('boot.%d' % boot_count): 569*9c5db199SXin Li boot_count += 1 570*9c5db199SXin Li msg = 'ADD: %s\nSubdir: %s\nTestname: %s\n%s' 571*9c5db199SXin Li msg %= (new_test.status, new_test.subdir, 572*9c5db199SXin Li new_test.testname, new_test.reason) 573*9c5db199SXin Li tko_utils.dprint(msg) 574*9c5db199SXin Li new_tests.append(new_test) 575*9c5db199SXin Li 576*9c5db199SXin Li if current_reason and not running_job.reason: 577*9c5db199SXin Li running_job.reason = current_reason 578*9c5db199SXin Li # The job is finished; produce the final SERVER_JOB entry and exit. 579*9c5db199SXin Li final_job = test.parse_test(self.job, '----', 'SERVER_JOB', 580*9c5db199SXin Li self.job.exit_status(), running_job.reason, 581*9c5db199SXin Li current_kernel, 582*9c5db199SXin Li self.job.started_time, 583*9c5db199SXin Li self.job.finished_time, 584*9c5db199SXin Li running_job) 585*9c5db199SXin Li new_tests.append(final_job) 586*9c5db199SXin Li yield new_tests 587