xref: /aosp_15_r20/external/autotest/tko/parsers/version_1.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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