xref: /aosp_15_r20/external/autotest/tko/parsers/version_0.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# pylint: disable=missing-docstring
3*9c5db199SXin Liimport os
4*9c5db199SXin Liimport re
5*9c5db199SXin Li
6*9c5db199SXin Liimport common
7*9c5db199SXin Lifrom autotest_lib.tko import models
8*9c5db199SXin Lifrom autotest_lib.tko import status_lib
9*9c5db199SXin Lifrom autotest_lib.tko import utils as tko_utils
10*9c5db199SXin Lifrom autotest_lib.tko.parsers import base
11*9c5db199SXin Li
12*9c5db199SXin Liclass NoHostnameError(Exception):
13*9c5db199SXin Li    pass
14*9c5db199SXin Li
15*9c5db199SXin Li
16*9c5db199SXin Liclass BoardLabelError(Exception):
17*9c5db199SXin Li    pass
18*9c5db199SXin Li
19*9c5db199SXin Li
20*9c5db199SXin Liclass job(models.job):
21*9c5db199SXin Li    def __init__(self, dir):
22*9c5db199SXin Li        job_dict = job.load_from_dir(dir)
23*9c5db199SXin Li        super(job, self).__init__(dir, **job_dict)
24*9c5db199SXin Li
25*9c5db199SXin Li
26*9c5db199SXin Li    @classmethod
27*9c5db199SXin Li    def load_from_dir(cls, dir):
28*9c5db199SXin Li        keyval = cls.read_keyval(dir)
29*9c5db199SXin Li        tko_utils.dprint(str(keyval))
30*9c5db199SXin Li
31*9c5db199SXin Li        user = keyval.get("user", None)
32*9c5db199SXin Li        label = keyval.get("label", None)
33*9c5db199SXin Li        queued_time = tko_utils.get_timestamp(keyval, "job_queued")
34*9c5db199SXin Li        started_time = tko_utils.get_timestamp(keyval, "job_started")
35*9c5db199SXin Li        finished_time = tko_utils.get_timestamp(keyval, "job_finished")
36*9c5db199SXin Li        machine = cls.determine_hostname(keyval, dir)
37*9c5db199SXin Li        machine_group = cls.determine_machine_group(machine, dir)
38*9c5db199SXin Li        machine_owner = keyval.get("owner", None)
39*9c5db199SXin Li
40*9c5db199SXin Li        aborted_by = keyval.get("aborted_by", None)
41*9c5db199SXin Li        aborted_at = tko_utils.get_timestamp(keyval, "aborted_on")
42*9c5db199SXin Li
43*9c5db199SXin Li        return {"user": user, "label": label, "machine": machine,
44*9c5db199SXin Li                "queued_time": queued_time, "started_time": started_time,
45*9c5db199SXin Li                "finished_time": finished_time, "machine_owner": machine_owner,
46*9c5db199SXin Li                "machine_group": machine_group, "aborted_by": aborted_by,
47*9c5db199SXin Li                "aborted_on": aborted_at, "keyval_dict": keyval}
48*9c5db199SXin Li
49*9c5db199SXin Li
50*9c5db199SXin Li    @classmethod
51*9c5db199SXin Li    def determine_hostname(cls, keyval, job_dir):
52*9c5db199SXin Li        host_group_name = keyval.get("host_group_name", None)
53*9c5db199SXin Li        machine = keyval.get("hostname", "")
54*9c5db199SXin Li        is_multimachine = "," in machine
55*9c5db199SXin Li
56*9c5db199SXin Li        # determine what hostname to use
57*9c5db199SXin Li        if host_group_name:
58*9c5db199SXin Li            if is_multimachine or not machine:
59*9c5db199SXin Li                tko_utils.dprint("Using host_group_name %r instead of "
60*9c5db199SXin Li                                 "machine name." % host_group_name)
61*9c5db199SXin Li                machine = host_group_name
62*9c5db199SXin Li        elif is_multimachine:
63*9c5db199SXin Li            try:
64*9c5db199SXin Li                machine = job.find_hostname(job_dir) # find a unique hostname
65*9c5db199SXin Li            except NoHostnameError:
66*9c5db199SXin Li                pass  # just use the comma-separated name
67*9c5db199SXin Li
68*9c5db199SXin Li        tko_utils.dprint("MACHINE NAME: %s" % machine)
69*9c5db199SXin Li        return machine
70*9c5db199SXin Li
71*9c5db199SXin Li
72*9c5db199SXin Li    @classmethod
73*9c5db199SXin Li    def determine_machine_group(cls, hostname, job_dir):
74*9c5db199SXin Li        machine_groups = set()
75*9c5db199SXin Li        for individual_hostname in hostname.split(","):
76*9c5db199SXin Li            host_keyval = models.test.parse_host_keyval(job_dir,
77*9c5db199SXin Li                                                        individual_hostname)
78*9c5db199SXin Li            if not host_keyval:
79*9c5db199SXin Li                tko_utils.dprint('Unable to parse host keyval for %s'
80*9c5db199SXin Li                                 % individual_hostname)
81*9c5db199SXin Li            elif 'labels' in host_keyval:
82*9c5db199SXin Li                # Use `model` label as machine group. This is to avoid the
83*9c5db199SXin Li                # confusion of multiple boards mapping to the same platform in
84*9c5db199SXin Li                # wmatrix. With this change, wmatrix will group tests with the
85*9c5db199SXin Li                # same model, rather than the same platform.
86*9c5db199SXin Li                labels = host_keyval['labels'].split(',')
87*9c5db199SXin Li                board_labels = [l[8:] for l in labels
88*9c5db199SXin Li                               if l.startswith('model%3A')]
89*9c5db199SXin Li                # If the host doesn't have `model:` label, fall back to `board:`
90*9c5db199SXin Li                # label.
91*9c5db199SXin Li                if not board_labels:
92*9c5db199SXin Li                    board_labels = [l[8:] for l in labels
93*9c5db199SXin Li                               if l.startswith('board%3A')]
94*9c5db199SXin Li                if board_labels:
95*9c5db199SXin Li                    # Multiple board/model labels aren't supposed to
96*9c5db199SXin Li                    # happen, but let's report something valid rather
97*9c5db199SXin Li                    # than just failing.
98*9c5db199SXin Li                    machine_groups.add(','.join(board_labels))
99*9c5db199SXin Li                else:
100*9c5db199SXin Li                    error = ('Failed to retrieve board label from host labels: '
101*9c5db199SXin Li                             '%s' % host_keyval['labels'])
102*9c5db199SXin Li                    tko_utils.dprint(error)
103*9c5db199SXin Li                    raise BoardLabelError(error)
104*9c5db199SXin Li            elif "platform" in host_keyval:
105*9c5db199SXin Li                machine_groups.add(host_keyval["platform"])
106*9c5db199SXin Li        machine_group = ",".join(sorted(machine_groups))
107*9c5db199SXin Li        tko_utils.dprint("MACHINE GROUP: %s" % machine_group)
108*9c5db199SXin Li        return machine_group
109*9c5db199SXin Li
110*9c5db199SXin Li
111*9c5db199SXin Li    @staticmethod
112*9c5db199SXin Li    def find_hostname(path):
113*9c5db199SXin Li        hostname = os.path.join(path, "sysinfo", "hostname")
114*9c5db199SXin Li        try:
115*9c5db199SXin Li            with open(hostname) as rf:
116*9c5db199SXin Li                machine = rf.readline().rstrip()
117*9c5db199SXin Li            return machine
118*9c5db199SXin Li        except Exception:
119*9c5db199SXin Li            tko_utils.dprint("Could not read a hostname from "
120*9c5db199SXin Li                             "sysinfo/hostname")
121*9c5db199SXin Li
122*9c5db199SXin Li        uname = os.path.join(path, "sysinfo", "uname_-a")
123*9c5db199SXin Li        try:
124*9c5db199SXin Li            machine = open(uname).readline().split()[1]
125*9c5db199SXin Li            return machine
126*9c5db199SXin Li        except Exception:
127*9c5db199SXin Li            tko_utils.dprint("Could not read a hostname from "
128*9c5db199SXin Li                             "sysinfo/uname_-a")
129*9c5db199SXin Li
130*9c5db199SXin Li        raise NoHostnameError("Unable to find a machine name")
131*9c5db199SXin Li
132*9c5db199SXin Li
133*9c5db199SXin Liclass kernel(models.kernel):
134*9c5db199SXin Li    def __init__(self, job, verify_ident=None):
135*9c5db199SXin Li        kernel_dict = kernel.load_from_dir(job.dir, verify_ident)
136*9c5db199SXin Li        super(kernel, self).__init__(**kernel_dict)
137*9c5db199SXin Li
138*9c5db199SXin Li
139*9c5db199SXin Li    @staticmethod
140*9c5db199SXin Li    def load_from_dir(dir, verify_ident=None):
141*9c5db199SXin Li        # try and load the booted kernel version
142*9c5db199SXin Li        attributes = False
143*9c5db199SXin Li        i = 1
144*9c5db199SXin Li        build_dir = os.path.join(dir, "build")
145*9c5db199SXin Li        while True:
146*9c5db199SXin Li            if not os.path.exists(build_dir):
147*9c5db199SXin Li                break
148*9c5db199SXin Li            build_log = os.path.join(build_dir, "debug", "build_log")
149*9c5db199SXin Li            attributes = kernel.load_from_build_log(build_log)
150*9c5db199SXin Li            if attributes:
151*9c5db199SXin Li                break
152*9c5db199SXin Li            i += 1
153*9c5db199SXin Li            build_dir = os.path.join(dir, "build.%d" % (i))
154*9c5db199SXin Li
155*9c5db199SXin Li        if not attributes:
156*9c5db199SXin Li            if verify_ident:
157*9c5db199SXin Li                base = verify_ident
158*9c5db199SXin Li            else:
159*9c5db199SXin Li                base = kernel.load_from_sysinfo(dir)
160*9c5db199SXin Li            patches = []
161*9c5db199SXin Li            hashes = []
162*9c5db199SXin Li        else:
163*9c5db199SXin Li            base, patches, hashes = attributes
164*9c5db199SXin Li        tko_utils.dprint("kernel.__init__() found kernel version %s"
165*9c5db199SXin Li                         % base)
166*9c5db199SXin Li
167*9c5db199SXin Li        # compute the kernel hash
168*9c5db199SXin Li        if base == "UNKNOWN":
169*9c5db199SXin Li            kernel_hash = "UNKNOWN"
170*9c5db199SXin Li        else:
171*9c5db199SXin Li            kernel_hash = kernel.compute_hash(base, hashes)
172*9c5db199SXin Li
173*9c5db199SXin Li        return {"base": base, "patches": patches,
174*9c5db199SXin Li                "kernel_hash": kernel_hash}
175*9c5db199SXin Li
176*9c5db199SXin Li
177*9c5db199SXin Li    @staticmethod
178*9c5db199SXin Li    def load_from_sysinfo(path):
179*9c5db199SXin Li        for subdir in ("reboot1", ""):
180*9c5db199SXin Li            uname_path = os.path.join(path, "sysinfo", subdir,
181*9c5db199SXin Li                                      "uname_-a")
182*9c5db199SXin Li            if not os.path.exists(uname_path):
183*9c5db199SXin Li                continue
184*9c5db199SXin Li            uname = open(uname_path).readline().split()
185*9c5db199SXin Li            return re.sub("-autotest$", "", uname[2])
186*9c5db199SXin Li        return "UNKNOWN"
187*9c5db199SXin Li
188*9c5db199SXin Li
189*9c5db199SXin Li    @staticmethod
190*9c5db199SXin Li    def load_from_build_log(path):
191*9c5db199SXin Li        if not os.path.exists(path):
192*9c5db199SXin Li            return None
193*9c5db199SXin Li
194*9c5db199SXin Li        base, patches, hashes = "UNKNOWN", [], []
195*9c5db199SXin Li        with open(path) as rf:
196*9c5db199SXin Li            lines = rf.readlines()
197*9c5db199SXin Li        for line in lines:
198*9c5db199SXin Li            head, rest = line.split(": ", 1)
199*9c5db199SXin Li            rest = rest.split()
200*9c5db199SXin Li            if head == "BASE":
201*9c5db199SXin Li                base = rest[0]
202*9c5db199SXin Li            elif head == "PATCH":
203*9c5db199SXin Li                patches.append(patch(*rest))
204*9c5db199SXin Li                hashes.append(rest[2])
205*9c5db199SXin Li        return base, patches, hashes
206*9c5db199SXin Li
207*9c5db199SXin Li
208*9c5db199SXin Liclass test(models.test):
209*9c5db199SXin Li    def __init__(self, subdir, testname, status, reason, test_kernel,
210*9c5db199SXin Li                 machine, started_time, finished_time, iterations,
211*9c5db199SXin Li                 attributes, labels):
212*9c5db199SXin Li        # for backwards compatibility with the original parser
213*9c5db199SXin Li        # implementation, if there is no test version we need a NULL
214*9c5db199SXin Li        # value to be used; also, if there is a version it should
215*9c5db199SXin Li        # be terminated by a newline
216*9c5db199SXin Li        if "version" in attributes:
217*9c5db199SXin Li            attributes["version"] = str(attributes["version"])
218*9c5db199SXin Li        else:
219*9c5db199SXin Li            attributes["version"] = None
220*9c5db199SXin Li
221*9c5db199SXin Li        super(test, self).__init__(subdir, testname, status, reason,
222*9c5db199SXin Li                                   test_kernel, machine, started_time,
223*9c5db199SXin Li                                   finished_time, iterations,
224*9c5db199SXin Li                                   attributes, labels)
225*9c5db199SXin Li
226*9c5db199SXin Li
227*9c5db199SXin Li    @staticmethod
228*9c5db199SXin Li    def load_iterations(keyval_path):
229*9c5db199SXin Li        return iteration.load_from_keyval(keyval_path)
230*9c5db199SXin Li
231*9c5db199SXin Li
232*9c5db199SXin Liclass patch(models.patch):
233*9c5db199SXin Li    def __init__(self, spec, reference, hash):
234*9c5db199SXin Li        tko_utils.dprint("PATCH::%s %s %s" % (spec, reference, hash))
235*9c5db199SXin Li        super(patch, self).__init__(spec, reference, hash)
236*9c5db199SXin Li        self.spec = spec
237*9c5db199SXin Li        self.reference = reference
238*9c5db199SXin Li        self.hash = hash
239*9c5db199SXin Li
240*9c5db199SXin Li
241*9c5db199SXin Liclass iteration(models.iteration):
242*9c5db199SXin Li    @staticmethod
243*9c5db199SXin Li    def parse_line_into_dicts(line, attr_dict, perf_dict):
244*9c5db199SXin Li        key, value = line.split("=", 1)
245*9c5db199SXin Li        perf_dict[key] = value
246*9c5db199SXin Li
247*9c5db199SXin Li
248*9c5db199SXin Liclass status_line(object):
249*9c5db199SXin Li    def __init__(self, indent, status, subdir, testname, reason,
250*9c5db199SXin Li                 optional_fields):
251*9c5db199SXin Li        # pull out the type & status of the line
252*9c5db199SXin Li        if status == "START":
253*9c5db199SXin Li            self.type = "START"
254*9c5db199SXin Li            self.status = None
255*9c5db199SXin Li        elif status.startswith("END "):
256*9c5db199SXin Li            self.type = "END"
257*9c5db199SXin Li            self.status = status[4:]
258*9c5db199SXin Li        else:
259*9c5db199SXin Li            self.type = "STATUS"
260*9c5db199SXin Li            self.status = status
261*9c5db199SXin Li        assert (self.status is None or
262*9c5db199SXin Li                self.status in status_lib.statuses)
263*9c5db199SXin Li
264*9c5db199SXin Li        # save all the other parameters
265*9c5db199SXin Li        self.indent = indent
266*9c5db199SXin Li        self.subdir = self.parse_name(subdir)
267*9c5db199SXin Li        self.testname = self.parse_name(testname)
268*9c5db199SXin Li        self.reason = reason
269*9c5db199SXin Li        self.optional_fields = optional_fields
270*9c5db199SXin Li
271*9c5db199SXin Li
272*9c5db199SXin Li    @staticmethod
273*9c5db199SXin Li    def parse_name(name):
274*9c5db199SXin Li        if name == "----":
275*9c5db199SXin Li            return None
276*9c5db199SXin Li        return name
277*9c5db199SXin Li
278*9c5db199SXin Li
279*9c5db199SXin Li    @staticmethod
280*9c5db199SXin Li    def is_status_line(line):
281*9c5db199SXin Li        return re.search(r"^\t*(\S[^\t]*\t){3}", line) is not None
282*9c5db199SXin Li
283*9c5db199SXin Li
284*9c5db199SXin Li    @classmethod
285*9c5db199SXin Li    def parse_line(cls, line):
286*9c5db199SXin Li        if not status_line.is_status_line(line):
287*9c5db199SXin Li            return None
288*9c5db199SXin Li        match = re.search(r"^(\t*)(.*)$", line, flags=re.DOTALL)
289*9c5db199SXin Li        if not match:
290*9c5db199SXin Li            # A more useful error message than:
291*9c5db199SXin Li            #  AttributeError: 'NoneType' object has no attribute 'groups'
292*9c5db199SXin Li            # to help us debug what happens on occasion here.
293*9c5db199SXin Li            raise RuntimeError("line %r could not be parsed." % line)
294*9c5db199SXin Li        indent, line = match.groups()
295*9c5db199SXin Li        indent = len(indent)
296*9c5db199SXin Li
297*9c5db199SXin Li        # split the line into the fixed and optional fields
298*9c5db199SXin Li        parts = line.rstrip("\n").split("\t")
299*9c5db199SXin Li
300*9c5db199SXin Li        part_index = 3
301*9c5db199SXin Li        status, subdir, testname = parts[0:part_index]
302*9c5db199SXin Li
303*9c5db199SXin Li        # all optional parts should be of the form "key=value". once we've found
304*9c5db199SXin Li        # a non-matching part, treat it and the rest of the parts as the reason.
305*9c5db199SXin Li        optional_fields = {}
306*9c5db199SXin Li        while part_index < len(parts):
307*9c5db199SXin Li            kv = re.search(r"^(\w+)=(.+)", parts[part_index])
308*9c5db199SXin Li            if not kv:
309*9c5db199SXin Li                break
310*9c5db199SXin Li
311*9c5db199SXin Li            optional_fields[kv.group(1)] = kv.group(2)
312*9c5db199SXin Li            part_index += 1
313*9c5db199SXin Li
314*9c5db199SXin Li        reason = "\t".join(parts[part_index:])
315*9c5db199SXin Li
316*9c5db199SXin Li        # build up a new status_line and return it
317*9c5db199SXin Li        return cls(indent, status, subdir, testname, reason,
318*9c5db199SXin Li                   optional_fields)
319*9c5db199SXin Li
320*9c5db199SXin Li
321*9c5db199SXin Liclass parser(base.parser):
322*9c5db199SXin Li    @staticmethod
323*9c5db199SXin Li    def make_job(dir):
324*9c5db199SXin Li        return job(dir)
325*9c5db199SXin Li
326*9c5db199SXin Li
327*9c5db199SXin Li    def state_iterator(self, buffer):
328*9c5db199SXin Li        new_tests = []
329*9c5db199SXin Li        boot_count = 0
330*9c5db199SXin Li        group_subdir = None
331*9c5db199SXin Li        sought_level = 0
332*9c5db199SXin Li        stack = status_lib.status_stack()
333*9c5db199SXin Li        current_kernel = kernel(self.job)
334*9c5db199SXin Li        boot_in_progress = False
335*9c5db199SXin Li        alert_pending = None
336*9c5db199SXin Li        started_time = None
337*9c5db199SXin Li
338*9c5db199SXin Li        while not self.finished or buffer.size():
339*9c5db199SXin Li            # stop processing once the buffer is empty
340*9c5db199SXin Li            if buffer.size() == 0:
341*9c5db199SXin Li                yield new_tests
342*9c5db199SXin Li                new_tests = []
343*9c5db199SXin Li                continue
344*9c5db199SXin Li
345*9c5db199SXin Li            # parse the next line
346*9c5db199SXin Li            line = buffer.get()
347*9c5db199SXin Li            tko_utils.dprint('\nSTATUS: ' + line.strip())
348*9c5db199SXin Li            line = status_line.parse_line(line)
349*9c5db199SXin Li            if line is None:
350*9c5db199SXin Li                tko_utils.dprint('non-status line, ignoring')
351*9c5db199SXin Li                continue # ignore non-status lines
352*9c5db199SXin Li
353*9c5db199SXin Li            # have we hit the job start line?
354*9c5db199SXin Li            if (line.type == "START" and not line.subdir and
355*9c5db199SXin Li                not line.testname):
356*9c5db199SXin Li                sought_level = 1
357*9c5db199SXin Li                tko_utils.dprint("found job level start "
358*9c5db199SXin Li                                 "marker, looking for level "
359*9c5db199SXin Li                                 "1 groups now")
360*9c5db199SXin Li                continue
361*9c5db199SXin Li
362*9c5db199SXin Li            # have we hit the job end line?
363*9c5db199SXin Li            if (line.type == "END" and not line.subdir and
364*9c5db199SXin Li                not line.testname):
365*9c5db199SXin Li                tko_utils.dprint("found job level end "
366*9c5db199SXin Li                                 "marker, looking for level "
367*9c5db199SXin Li                                 "0 lines now")
368*9c5db199SXin Li                sought_level = 0
369*9c5db199SXin Li
370*9c5db199SXin Li            # START line, just push another layer on to the stack
371*9c5db199SXin Li            # and grab the start time if this is at the job level
372*9c5db199SXin Li            # we're currently seeking
373*9c5db199SXin Li            if line.type == "START":
374*9c5db199SXin Li                group_subdir = None
375*9c5db199SXin Li                stack.start()
376*9c5db199SXin Li                if line.indent == sought_level:
377*9c5db199SXin Li                    started_time = \
378*9c5db199SXin Li                                 tko_utils.get_timestamp(
379*9c5db199SXin Li                        line.optional_fields, "timestamp")
380*9c5db199SXin Li                tko_utils.dprint("start line, ignoring")
381*9c5db199SXin Li                continue
382*9c5db199SXin Li            # otherwise, update the status on the stack
383*9c5db199SXin Li            else:
384*9c5db199SXin Li                tko_utils.dprint("GROPE_STATUS: %s" %
385*9c5db199SXin Li                                 [stack.current_status(),
386*9c5db199SXin Li                                  line.status, line.subdir,
387*9c5db199SXin Li                                  line.testname, line.reason])
388*9c5db199SXin Li                stack.update(line.status)
389*9c5db199SXin Li
390*9c5db199SXin Li            if line.status == "ALERT":
391*9c5db199SXin Li                tko_utils.dprint("job level alert, recording")
392*9c5db199SXin Li                alert_pending = line.reason
393*9c5db199SXin Li                continue
394*9c5db199SXin Li
395*9c5db199SXin Li            # ignore Autotest.install => GOOD lines
396*9c5db199SXin Li            if (line.testname == "Autotest.install" and
397*9c5db199SXin Li                line.status == "GOOD"):
398*9c5db199SXin Li                tko_utils.dprint("Successful Autotest "
399*9c5db199SXin Li                                 "install, ignoring")
400*9c5db199SXin Li                continue
401*9c5db199SXin Li
402*9c5db199SXin Li            # ignore END lines for a reboot group
403*9c5db199SXin Li            if (line.testname == "reboot" and line.type == "END"):
404*9c5db199SXin Li                tko_utils.dprint("reboot group, ignoring")
405*9c5db199SXin Li                continue
406*9c5db199SXin Li
407*9c5db199SXin Li            # convert job-level ABORTs into a 'CLIENT_JOB' test, and
408*9c5db199SXin Li            # ignore other job-level events
409*9c5db199SXin Li            if line.testname is None:
410*9c5db199SXin Li                if (line.status == "ABORT" and
411*9c5db199SXin Li                    line.type != "END"):
412*9c5db199SXin Li                    line.testname = "CLIENT_JOB"
413*9c5db199SXin Li                else:
414*9c5db199SXin Li                    tko_utils.dprint("job level event, "
415*9c5db199SXin Li                                    "ignoring")
416*9c5db199SXin Li                    continue
417*9c5db199SXin Li
418*9c5db199SXin Li            # use the group subdir for END lines
419*9c5db199SXin Li            if line.type == "END":
420*9c5db199SXin Li                line.subdir = group_subdir
421*9c5db199SXin Li
422*9c5db199SXin Li            # are we inside a block group?
423*9c5db199SXin Li            if (line.indent != sought_level and
424*9c5db199SXin Li                line.status != "ABORT" and
425*9c5db199SXin Li                not line.testname.startswith('reboot.')):
426*9c5db199SXin Li                if line.subdir:
427*9c5db199SXin Li                    tko_utils.dprint("set group_subdir: "
428*9c5db199SXin Li                                     + line.subdir)
429*9c5db199SXin Li                    group_subdir = line.subdir
430*9c5db199SXin Li                tko_utils.dprint("ignoring incorrect indent "
431*9c5db199SXin Li                                 "level %d != %d," %
432*9c5db199SXin Li                                 (line.indent, sought_level))
433*9c5db199SXin Li                continue
434*9c5db199SXin Li
435*9c5db199SXin Li            # use the subdir as the testname, except for
436*9c5db199SXin Li            # boot.* and kernel.* tests
437*9c5db199SXin Li            if (line.testname is None or
438*9c5db199SXin Li                not re.search(r"^(boot(\.\d+)?$|kernel\.)",
439*9c5db199SXin Li                              line.testname)):
440*9c5db199SXin Li                if line.subdir and '.' in line.subdir:
441*9c5db199SXin Li                    line.testname = line.subdir
442*9c5db199SXin Li
443*9c5db199SXin Li            # has a reboot started?
444*9c5db199SXin Li            if line.testname == "reboot.start":
445*9c5db199SXin Li                started_time = tko_utils.get_timestamp(
446*9c5db199SXin Li                    line.optional_fields, "timestamp")
447*9c5db199SXin Li                tko_utils.dprint("reboot start event, "
448*9c5db199SXin Li                                 "ignoring")
449*9c5db199SXin Li                boot_in_progress = True
450*9c5db199SXin Li                continue
451*9c5db199SXin Li
452*9c5db199SXin Li            # has a reboot finished?
453*9c5db199SXin Li            if line.testname == "reboot.verify":
454*9c5db199SXin Li                line.testname = "boot.%d" % boot_count
455*9c5db199SXin Li                tko_utils.dprint("reboot verified")
456*9c5db199SXin Li                boot_in_progress = False
457*9c5db199SXin Li                verify_ident = line.reason.strip()
458*9c5db199SXin Li                current_kernel = kernel(self.job, verify_ident)
459*9c5db199SXin Li                boot_count += 1
460*9c5db199SXin Li
461*9c5db199SXin Li            if alert_pending:
462*9c5db199SXin Li                line.status = "ALERT"
463*9c5db199SXin Li                line.reason = alert_pending
464*9c5db199SXin Li                alert_pending = None
465*9c5db199SXin Li
466*9c5db199SXin Li            # create the actual test object
467*9c5db199SXin Li            finished_time = tko_utils.get_timestamp(
468*9c5db199SXin Li                line.optional_fields, "timestamp")
469*9c5db199SXin Li            final_status = stack.end()
470*9c5db199SXin Li            tko_utils.dprint("Adding: "
471*9c5db199SXin Li                             "%s\nSubdir:%s\nTestname:%s\n%s" %
472*9c5db199SXin Li                             (final_status, line.subdir,
473*9c5db199SXin Li                              line.testname, line.reason))
474*9c5db199SXin Li            new_test = test.parse_test(self.job, line.subdir,
475*9c5db199SXin Li                                       line.testname,
476*9c5db199SXin Li                                       final_status, line.reason,
477*9c5db199SXin Li                                       current_kernel,
478*9c5db199SXin Li                                       started_time,
479*9c5db199SXin Li                                       finished_time)
480*9c5db199SXin Li            started_time = None
481*9c5db199SXin Li            new_tests.append(new_test)
482*9c5db199SXin Li
483*9c5db199SXin Li        # the job is finished, but we never came back from reboot
484*9c5db199SXin Li        if boot_in_progress:
485*9c5db199SXin Li            testname = "boot.%d" % boot_count
486*9c5db199SXin Li            reason = "machine did not return from reboot"
487*9c5db199SXin Li            tko_utils.dprint(("Adding: ABORT\nSubdir:----\n"
488*9c5db199SXin Li                              "Testname:%s\n%s")
489*9c5db199SXin Li                             % (testname, reason))
490*9c5db199SXin Li            new_test = test.parse_test(self.job, None, testname,
491*9c5db199SXin Li                                       "ABORT", reason,
492*9c5db199SXin Li                                       current_kernel, None, None)
493*9c5db199SXin Li            new_tests.append(new_test)
494*9c5db199SXin Li        yield new_tests
495