Ejemplo n.º 1
0
 def _scan_t_subcases(self, path, prefix):
     # we just create here the list of parameters we'll use to
     # create the subtestcase on every execution of the testcase
     # itself (done in configure_10 when the TC is executed). Read
     # that for reasons...
     with open(path) as tf:
         subcases = re.findall(self._regex_t_subcases, tf.read())
     for name in subcases:
         # make sure to remove leading/trailing whitespace and then
         # trailing _--this allows it to match what the bats tool
         # scans, which we'll need to match in the output of
         # tap_parse_output() in _eval_one()
         _name = commonl.name_make_safe(name.strip()).rstrip("_")
         t_file_name = os.path.basename(path)
         self.subtc_list.append((
             # note we'll key on the .t file basename and the subtest name
             # inside the .t file, otherwise if two different .t files
             # in the dir have same subtest names, they'd override.
             # In _eval_one() we'll refer to this dict
             # We keep this, even if is basically the same because
             # it will easily make configure_10 later print a
             # shorter list for reference in reports...
             t_file_name + "." + _name,
             # testcase full name / unique ID
             prefix + t_file_name.replace(".t", "") + "." + _name,
             self.kws['thisfile'], self.origin
         ))
Ejemplo n.º 2
0
    def _eval_one(self, target, t_file, prefix = ""):
        result = tcfl.tc.result_c(0, 0, 0, 0, 0)
        self.report_info("running %s%s" % (prefix, t_file))
        self.kw_set("t_file", t_file)
        output = target.shell.run(
            # remember we cd'ed into the directory
            "bats --tap %s || echo FAI''LED-%s"
            % (t_file, self.kws['tc_hash']),
            output = True)
        if 'bats: command not found' in output:
            raise tcfl.tc.blocked_e(
                "'bats' tool not installed in the target",
                dict(target = target, output = output))
        # top level result
        if 'FAILED-%(tc_hash)s' % self.kws in output:
            result.failed += 1
        else:
            result.passed += 1

        # seems we had execution, so let's parse the output and
        # make subcases
        tcs = tap_parse_output(output)
        for name, data in tcs.iteritems():
            _name = commonl.name_make_safe(name)
            t_file_name = t_file.replace(".t", "")
            subcase = tc_taps_subcase_c_base(
                prefix + t_file_name + "." + _name,
                self.kws['thisfile'], self.origin,
                self, data)
            self.post_tc_append(subcase)
            result += subcase.result
        return result
Ejemplo n.º 3
0
 def __init__(self, path, t_file_path):
     tcfl.tc.tc_c.__init__(self, commonl.name_make_safe(path),
                           # these two count as the ones that started this
                           path, t_file_path)
     # t_file_path goes to self.kws['thisfile'], name to self.name
     # and self.kws['tc_name']
     self.image = None
     self.rel_path_in_target = None
     self.t_files = [ t_file_path ]
Ejemplo n.º 4
0
 def __init__(self, target, template_image_filename, capturer,
              in_area, merge_similar, min_width, min_height,
              poll_period, timeout, raise_on_timeout, raise_on_found,
              name = None):
     if not image_expectation_works:
         raise RuntimeError("Image matching won't work; need packages"
                            " cv2, imutils, numpy")
     assert isinstance(target, tc.target_c)
     assert isinstance(capturer, basestring)
     assert in_area == None \
         or isinstance(in_area, collections.Iterable) \
         and len(in_area) == 4 \
         and all(i >= 0 and i <= 1 for i in in_area), \
         'in_area parameter must be a tuple of four numbers ' \
         'from 0 to 1 describing the upper-left and lower-right ' \
         'of the area where to look for the image, ' \
         'eg: (0, 0, 0.5, 0.5) means in the top left quarter'
     assert merge_similar >= 0.0 and merge_similar <= 1.0, \
         "merge_similar has to be a float from 0.0 to 1.0"
     assert name == None or isinstance(name, basestring)
     tc.expectation_c.__init__(self, target, poll_period, timeout,
                               raise_on_timeout = raise_on_timeout,
                               raise_on_found = raise_on_found)
     self.capturer = capturer
     self.in_area = in_area
     self.merge_similar = merge_similar
     # if the image is relative, we make it relative to the
     # filename of the caller--so we can do short names
     if name:
         self.name = name
     else:
         self.name = commonl.name_make_safe(template_image_filename)
     if os.path.isabs(template_image_filename):
         self.template_image_filename = template_image_filename
     else:
         self.template_image_filename = os.path.join(
             # 2 up the stack is the guy who called
             # target.capture.image_on_screeshot()
             os.path.dirname(inspect.stack()[2][1]),
             template_image_filename)
     with open(self.template_image_filename) as _f:
         # try to open it, cv2.imread() is quite crappy at giving
         # errors on file not found
         pass
     self.template_img = cv2.imread(self.template_image_filename,
                                    cv2.IMREAD_GRAYSCALE)
     # FIXME: raise exception if too small
     self.min_width = min_width
     self.min_height = min_height
Ejemplo n.º 5
0
 def _scan_t_subcases(self, path, prefix):
     # we just create here the list of parameters we'll use to
     # create the subtestcase on every execution of the testcase
     # itself (done in configure_10 when the TC is executed). Read
     # that for reasons...
     with open(path) as tf:
         subcases = re.findall(self._regex_t_subcases, tf.read())
     for name in subcases:
         # here we need to be careful to treat the scanned name
         # exactly the same way the bats tool will do it, otherwise
         # when we scan them from the bats output, they won't match
         name = name.strip()
         # strip the simple or double quotes from the name
         # -> "Starting somethings --id=\"blah\""
         # <- Starting somethings --id=\"blah\"
         name = name[1:-1]
         # ok, now somethings might have been escaped with \...we
         # can ignore it, since we are not affected by it...
         # -> Starting somethings --id=\"blah\"
         # <- Starting somethings --id="blah"
         name = name.replace("\\", "")
         # strip here, as BATS will do too
         _name = commonl.name_make_safe(name.strip())
         logging.debug("%s contains subcase %s", path, _name)
         t_file_name = os.path.basename(path)
         self.subtc_list.append((
             # note we'll key on the .t file basename and the subtest name
             # inside the .t file, otherwise if two different .t files
             # in the dir have same subtest names, they'd override.
             # In _eval_one() we'll refer to this dict
             # We keep this, even if is basically the same because
             # it will easily make configure_10 later print a
             # shorter list for reference in reports...
             t_file_name + "." + _name,
             # testcase full name / unique ID
             prefix + t_file_name.replace(".t", "") + "." + _name,
             self.kws['thisfile'],
             self.origin))
Ejemplo n.º 6
0
    def _eval_one(self, target, t_file, prefix):
        result = tcfl.tc.result_c(0, 0, 0, 0, 0)
        rel_file_path = os.path.join(prefix, t_file)
        if self._ts_ignore(rel_file_path):
            target.report_skip(
                "%s: skipped due to configuration "
                "(tcfl.tc_clear_bbt.ignore_ts or BBT_IGNORE_TS environment)"
                % rel_file_path)
            for name, subtc in self.subtc.iteritems():
                if name.startswith(t_file):
                    subtc.result = tcfl.tc.result_c(0, 0, 0, 0, 1)
                    subtc.data = dict(result = "skipped")
                    subtc.update(
                        tcfl.tc.result_c(0, 0, 0, 0, 1),
                        "skipped due to configuration"
                        " (tcfl.tc_clear_bbt.ignore_ts or"
                        " BBT_IGNORE_TS environment)",
                        "")
            result.skipped += 1
        else:
            self.report_info("running %s%s" % (prefix, t_file))
            self.kw_set("t_file", t_file)

            # patch any execution hot fixes
            pre_sh_l = []
            if self.test_bundle_name in bundle_run_pre_sh:
                self.report_info("adding configured pre_sh steps from "
                                 "tcfl.tc_clear_bbt.bundle_run_pre_sh[%s]"
                                 % self.test_bundle_name)
                pre_sh_l += bundle_run_pre_sh[self.test_bundle_name]
            if t_file in bundle_run_pre_sh:
                self.report_info("adding configured pre_sh steps from "
                                 "tcfl.tc_clear_bbt.bundle_run_pre_sh[%s]"
                                 % t_file)
                pre_sh_l += bundle_run_pre_sh[t_file]
            for pre_sh in pre_sh_l:
                target.shell.run(pre_sh % self.kws)

            # Run the t_file
            # remember we cd'ed into the directory, the way these
            # BBTs are written, it is expected
            if self.bats_parallel and 'use_parallel' in t_file:
                # note we set CUPS in the target in start()
                parallel = "-j $CPUS"
            else:
                parallel = ""
            run_timeout = _bundle_run_timeout(self, self.test_bundle_name, t_file)
            output = target.shell.run(
                "bats --tap %s %s || echo FAILED''-%s"
                % (t_file, parallel, self.kws['tc_hash']),
                output = True, timeout = run_timeout)
            # top level result
            if 'bats: command not found' in output:
                self.report_error(
                    "'bats' tool not installed in the target",
                    dict(target = target, output = output))
                result.errors += 1
            elif 'FAILED-%(tc_hash)s' % self.kws in output:
                result.failed += 1
            else:
                result.passed += 1

            # seems we had execution, so let's parse the output and
            # make subcases of the .t -- if anything fails, catch and
            # convert to a TCF exception so it only affects *this*
            # testcase in the result accounting--note
            # report_from_exception() will report exceptio data so we
            # can debug if it is an infra or TC problem.
            try:
                tcs = tap_parse_output(output)
            except Exception as e:
                tcs = dict()
                result += tcfl.tc.result_c.report_from_exception(self, e)
            for name, data in tcs.iteritems():
                # get the subtc; see _scan_t_subcases() were we keyed
                # them in
                _name = commonl.name_make_safe(name.strip())
                tc_name = t_file + "." + _name
                if tc_name in bundle_t_map:
                    _tc_name = bundle_t_map[tc_name]
                    self.report_info("subtestcase name %s mapped to %s "
                                     "per configuration "
                                     "tcfl.tc_clear_bbt.bundle_t_map"
                                     % (tc_name, _tc_name))
                else:
                    _tc_name = tc_name
                subtc = self.subtc[_tc_name]
                if self._ts_ignore(subtc.name):
                    _result = tcfl.tc.result_c(0, 0, 0, 0, 1)
                    summary = "result skipped due to configuration " \
                        "(tcfl.tc_clear_bbt.ignore_ts or " \
                        "BBT_IGNORE_TS environment)"
                    log = data['output']
                else:
                    # translate the taps result to a TCF result, record it
                    _result = self.mapping[data['result']]
                    log = data['output']
                    summary = log.split('\n', 1)[0]
                subtc.update(_result, summary, log, )
                result += subtc.result
        return result
Ejemplo n.º 7
0
    def _output_parse(self, testsuite, suite_tc, lf):

        #
        # Parse the log file out of running a single test suite; the
        # file format is more or less
        #
        # START: ptest-runner
        # <timestamp>
        # BEGIN: <suite>
        # <output for subtc>
        # FAIL|SKIP|PASS: <subtc>
        # <output for subtc>
        # FAIL|SKIP|PASS: <subtc>
        # <output for subtc>
        # FAIL|SKIP|PASS: <subtc>
        # DURATION: <n>
        # END: <suite>
        # <timestamp>
        # STOP: ptest-runner
        #
        result = tcfl.tc.result_c()
        cnt = -1
        start_seen = False
        suite = None
        log = ""
        date_regex = re.compile("^[-:T0-9]+$")	# lazy YYYY-MM-DDTHH:MM
        for line in lf:
            line = line.rstrip()
            cnt += 1
            if line.startswith("START: "):
                _, name = line.split(None, 1)
                assert name == 'ptest-runner', \
                    "%d: Found START for %s, not ptest-runner" % (cnt, name)
                start_seen = True
            elif line.startswith("STOP:"):
                assert start_seen, "%d: Found STOP: without START" % cnt
                _, name = line.split(None, 1)
                assert name == 'ptest-runner', \
                    "%d: Found STOP for %s, not ptest-runner" % (cnt, name)
                start_seen = False
            elif date_regex.search(line):
                pass	# ignored
            elif line.startswith("BEGIN:"):
                assert start_seen, "%d: Found BEGIN: without START" % cnt
                _, suite = line.split(None, 1)
                if suite != testsuite \
                   and testsuite not in suite:	# /usr/lib/SUITE/ptest HACK
                    raise AssertionError(
                        "%d: Found BEGIN: for %s, expected %s" \
                        % (cnt, suite, testsuite))
                log = ""
            elif line.startswith("END:"):
                assert start_seen, "%d: Found END: without START" % cnt
                assert suite, "%d: Found END: without suite" % cnt
                _, _suite = line.split(None, 1)
                assert suite == _suite, \
                    "%d: Found END: for suite %s different to" \
                    " START:'s %s" % (cnt, _suite, suite)
                suite = None
            elif line.startswith("PASS:"******"FAIL:") \
                 or line.startswith("SKIP:"):
                tag, subcase = line.split(None, 1)
                assert start_seen, "%d: Found %s without START" % cnt
                assert suite, "%d: Found %s without suite" % cnt
                # some tests print names that are ...hummm...useless
                subcase_safe = commonl.name_make_safe(subcase)
                if tag == "PASS:"******"FAIL:":
                    _result = tcfl.tc.result_c(failed = 1)
                elif tag == "SKIP:":
                    _result = tcfl.tc.result_c(skipped = 1)
                else:
                    raise AssertionError("unknown tag from command output '%s'"
                                         % tag)
                #
                # Create a subtestcase with the information we found
                # about the execution of this SUBCASE of this SUITE
                #
                name = self.name + "##%s/%s" % (testsuite, subcase_safe)
                subtc = tcfl.tc.subtc_c(name, self.kws['thisfile'],
                                        suite, self)
                subtc.update(_result, line, log)
                result += _result
                self.subtc[subcase] = subtc
                log = ""
            elif line.startswith("DURATION:"):
                pass	# ignored
            else:
                log += line + "\n"

        # well, we done -- check thinks are as they should be, test
        # closed and all
        if suite_tc.result:
            # no wait, we have already updated the suite testcase
            # data; this was probably a timeout, so we know bad stuff
            # happened 
            return
        if suite:
            # this should have been closed by a END: tag
            result.errors += 1
            suite_tc.update(result,
                            "testsuite probably didn't complete"
                            " execution, no END tag", log)
        elif start_seen:
            # this should have been closed by a STOP tag
            result.errors += 1
            suite_tc.update(result,
                            "testsuite probably didn't complete"
                            " execution, no STOP tag", log)
        else:
            result.passed += 1
            lf.seek(0, 0)
            suite_tc.update(result, "testsuite completed execution",
                            lf.read())
Ejemplo n.º 8
0
    def flash_spec_parse(self, flash_image_s=None):
        """Parse a images to flash specification in a string (that might be
        taken from the environment

        The string describing what to flash is in the form::

          [[no-]soft] [[no-]upload] IMAGE:NAME[ IMAGE:NAME[..]]]

        - *soft*: flash in soft mode (default *False) (see
          :meth:`target.images.flash
          <tcfl.target_ext_images.extension.flash>`) the image(s) will
          only be flashed if the image to be flashed is different than the
          last image that was flashed.

        - *upload*: flash in soft mode (default *True*) (see
          :meth:`target.images.flash
          <tcfl.target_ext_images.extension.flash>`). The file will be
          uploaded to the server first or it will be assumed it is already
          present in the server.

        - *IMAGETYPE:FILENAME* flash file *FILENAME* in flash destination
          *IMAGETYPE*; *IMAGETYPE* being any of the image
          destinations the target can flash; can be found with::

            $ tcf image-ls TARGETNAME

          or from the inventory::

            $ tcf get TARGETNAME -p interfaces.images

        The string specification will be taken, in this order from the
        following list

        - the *image_flash* parameter

        - environment *IMAGE_FLASH_<TYPE>*

        - environment *IMAGE_FLASH_<FULLID>*

        - environment *IMAGE_FLASH_<ID>*

        - environment *IMAGE_FLASH*

        With *TYPE*, *FULLID* and *ID* sanitized to any character outside
        of the ranges *[a-zA-Z0-9_]* replaced with an underscore (*_*).

        **Example**

        ::

          $ export IMAGE_FLASH_TYPE1="soft bios:path/to/bios.bin"
          $ tcf run test_SOMESCRIPT.py

        if *test_SOMESCRIPT.py* uses this template, every invocation of it
        on a machine of type TYPE1 will result on the file
        *path/to/bios.bin* being flashed on the *bios* location (however,
        because of *soft*, if it was already flashed before, it will be
        skipped).

        :return: a tuple of

          >>> ( DICT, UPLOAD, SOFT )

          - *DICT*: Dictionary is a dictionary of IMAGETYPE/file name to flash:

            >>> {
            >>>     IMGTYPE1: IMGFILE1,
            >>>     IMGTYPE2: IMGFILE2,
            >>>     ...
            >>> }

          - *UPLOAD*: boolean indicating if the user wants the files to be
            uploaded (*True*, default) or to assume they are already in
            the server (*False*).

          - *SOFT*: flash in soft mode (*True*) or not (*False*, default
            if not given).

        """
        target = self.target
        if not flash_image_s:

            # empty, load from environment
            target_id_safe = commonl.name_make_safe(
                target.id, string.ascii_letters + string.digits)
            target_fullid_safe = commonl.name_make_safe(
                target.fullid, string.ascii_letters + string.digits)
            target_type_safe = commonl.name_make_safe(
                target.type, string.ascii_letters + string.digits)

            source = None  # keep pylint happy
            sourcel = [
                # go from most specifcy to most generic
                "IMAGE_FLASH_%s" % target_fullid_safe,
                "IMAGE_FLASH_%s" % target_id_safe,
                "IMAGE_FLASH_%s" % target_type_safe,
                "IMAGE_FLASH",
            ]
            for source in sourcel:
                flash_image_s = os.environ.get(source, None)
                if flash_image_s != None:
                    break
            else:
                target.report_info(
                    "skipping image flashing (no function argument nor environment: %s)"
                    % " ".join(sourcel))
                return {}, False, False
        else:
            source = "function argument"

        # verify the format
        if not self._image_flash_regex.search(flash_image_s):
            raise tc.blocked_e(
                "image specification in %s does not conform to the form"
                " [[no-]soft] [[no-]upload] IMAGE:NAME[ IMAGE:NAME[..]]]" %
                source, dict(target=target))

        image_flash = {}
        soft = False
        upload = True
        for entry in flash_image_s.split(" "):
            if not entry:  # empty spaces...welp
                continue
            if entry == "soft":
                soft = True
                continue
            if entry == "no-soft":
                soft = False
                continue
            if entry == "upload":
                upload = True
                continue
            if entry == "no-upload":
                upload = False
                continue
            name, value = entry.split(":", 1)
            image_flash[name] = value

        return image_flash, upload, soft
Ejemplo n.º 9
0
    def flash_spec_parse(self, flash_image_s=None, env_prefix="IMAGE_FLASH"):
        """Parse a images to flash specification in a string (that might be
        taken from the environment

        The string describing what to flash is in the form::

          [[no-]soft] [[no-]upload] IMAGE:NAME[ IMAGE:NAME[..]]]

        - *soft*: flash in soft mode (default *False) (see
          :meth:`target.images.flash
          <tcfl.target_ext_images.extension.flash>`) the image(s) will
          only be flashed if the image to be flashed is different than the
          last image that was flashed.

        - *upload*: flash in soft mode (default *True*) (see
          :meth:`target.images.flash
          <tcfl.target_ext_images.extension.flash>`). The file will be
          uploaded to the server first or it will be assumed it is already
          present in the server.

        - *IMAGETYPE:FILENAME* flash file *FILENAME* in flash destination
          *IMAGETYPE*; *IMAGETYPE* being any of the image
          destinations the target can flash; can be found with::

            $ tcf image-ls TARGETNAME

          or from the inventory::

            $ tcf get TARGETNAME -p interfaces.images

          Note that the specification can include strings *%(FIELD)s* to be
          replaced by values extracted from the inventory and
          formatted using Python % formatting. This resolution will
          recurse up to five times (eg: if string *some-%(FIELD1)s* resolves
          into *some-thing-%(FIELD2)s* and expansion of *FIELD2*
          yields *some-thing-nice*).

        The string specification will be taken, in this order from the
        following list

        - the *image_flash* parameter

        - environment *IMAGE_FLASH_<TYPE>*

        - environment *IMAGE_FLASH_<FULLID>*

        - environment *IMAGE_FLASH_<ID>*

        - environment *IMAGE_FLASH*

        With *TYPE*, *FULLID* and *ID* sanitized to any character outside
        of the ranges *[a-zA-Z0-9_]* replaced with an underscore (*_*).

        **Example**

        ::

          $ export IMAGE_FLASH_TYPE1="soft bios:path/to/bios.bin"
          $ tcf run test_SOMESCRIPT.py

        if *test_SOMESCRIPT.py* uses this template, every invocation of it
        on a machine of type TYPE1 will result on the file
        *path/to/bios.bin* being flashed on the *bios* location (however,
        because of *soft*, if it was already flashed before, it will be
        skipped).

        :return: a tuple of

          >>> ( DICT, UPLOAD, SOFT )

          - *DICT*: Dictionary is a dictionary of IMAGETYPE/file name to flash:

            >>> {
            >>>     IMGTYPE1: IMGFILE1,
            >>>     IMGTYPE2: IMGFILE2,
            >>>     ...
            >>> }

          - *UPLOAD*: boolean indicating if the user wants the files to be
            uploaded (*True*, default) or to assume they are already in
            the server (*False*).

          - *SOFT*: flash in soft mode (*True*) or not (*False*, default
            if not given).

        """
        target = self.target
        if not flash_image_s:

            # empty, load from environment
            target_id_safe = commonl.name_make_safe(
                target.id, string.ascii_letters + string.digits)
            target_fullid_safe = commonl.name_make_safe(
                target.fullid, string.ascii_letters + string.digits)
            target_type_safe = commonl.name_make_safe(
                target.type, string.ascii_letters + string.digits)

            source = None  # keep pylint happy
            sourcel = [
                # go from most specifcy to most generic
                # IMAGE_FLASH_SERVER_NAME
                # IMAGE_FLASH_NAME
                # IMAGE_FLASH_TYPE
                # IMAGE_FLASH
                f"{env_prefix}_{target_fullid_safe}",
                f"{env_prefix}_{target_id_safe}",
                f"{env_prefix}_{target_type_safe}",
                env_prefix,
            ]
            for source in sourcel:
                flash_image_s = os.environ.get(source, None)
                if flash_image_s != None:
                    break
            else:
                target.report_info(
                    "skipping image flashing (no function argument nor environment: %s)"
                    % " ".join(sourcel))
                return {}, False, False
        else:
            source = "function argument"

        # verify the format
        if not self._image_flash_regex.search(flash_image_s):
            raise tc.blocked_e(
                "image specification in %s does not conform to the form"
                " [[no-]soft] [[no-]upload] [IMAGE:]NAME[ [IMAGE:]NAME[..]]]" %
                source, dict(target=target))

        image_flash = {}
        soft = False
        upload = True
        for entry in flash_image_s.split(" "):
            if not entry:  # empty spaces...welp
                continue
            if entry == "soft":
                soft = True
                continue
            if entry == "no-soft":
                soft = False
                continue
            if entry == "upload":
                upload = True
                continue
            if entry == "no-upload":
                upload = False
                continue
            # Expand keywords, so we can do things like
            # bios:/BIOSDIR/%(type)s/LATEST-%(bsps.x86_64.cpu-CPU0.family)s
            entry = commonl.kws_expand(entry, target.kws)
            # see if we can assume this is in the form
            # [SOMEPATH/]FILENAME[.EXT]; if FILENAME matches a known
            # flashing destination, we take it
            if ":" in entry:
                name, value = entry.split(":", 1)
                image_flash[name] = value
            else:
                basename, _ext = os.path.splitext(os.path.basename(entry))
                # if the inventory publishes a
                # interfaces.images.basename, this means we have a
                # flasher where this can go
                image_typel = list(
                    target.rt.get("interfaces", {}).get("images", {}))
                for image_type in image_typel:
                    if basename.startswith(image_type):
                        image_flash[image_type] = entry
                        break
                else:
                    raise RuntimeError(
                        "%s: can't auto-guess destination for this "
                        "file, please prefix IMAGETYPE: "
                        "(known are: %s)" % (entry, " ".join(image_typel)))

        return image_flash, upload, soft
Ejemplo n.º 10
0
    def eval_00(self, ic, target):

        target.shell.prompt_regex = re.compile(r"KTS-PROMPT[\$%#]")
        target.shell.run(
            f"echo $PS1 | grep -q KTS-PROMPT"
            f" || export PS1='TCF-{self.ticket}:KTS-PROMPT$ '"
            f" # a simple prompt is harder to confuse with general output")

        if not self.subcases:
            raise RuntimeError("FIXME: no subcases")
        tcfl.tl.linux_time_set(target)

        # check if stdbuf is available to use it if so
        output = target.shell.run(
            "stdbuf --help > /dev/null || echo N''OPE # stdbuf available??",
            output=True,
            trim=True)
        if 'NOPE' in output:
            stdbuf_prefix = ""
        else:
            # run the command prefixing this to reduce the
            # buffering; otherwise suites that take too long will
            # see their ouput buffered in tee and
            # resulting in timeouts expecting progress
            # stdbuf is part of GNU coreutils, so most places have
            # it installed.
            stdbuf_prefix = "stdbuf -e0 -o0"

        target.shell.run("rm -rf logs && mkdir logs")
        target.shell.run("set -o pipefail")
        # FIXME: running serial -> add way to run parallel
        for testsuite in self.subcases:
            timeout = timeouts.get(
                testsuite, int(os.environ.get("TIMEOUT", timeout_default)))
            # suite_tc -> overall result of the whole testsuite execution
            with self.subcase(testsuite):
                try:
                    target.report_info("running testsuite %s" % testsuite)
                    target.shell.run(
                        f"mkdir -p 'logs/{os.path.dirname(testsuite)}'")
                    # run the testsuite and capture the output to a log file
                    # HACK: ignore errors, so we can cancel on timeout
                    # without it becoming a mess -- still
                    target.shell.run(
                        f"podman kill --signal KILL {self.runid_hashid} || true"
                    )
                    target.shell.run(
                        f"podman rm -f {self.runid_hashid} || true")
                    target.shell.run(
                        f"{stdbuf_prefix} podman run --name {self.runid_hashid} {testsuite} 2>&1"
                        f" | tee -a logs/{testsuite}.log",
                        timeout=timeout)
                except tcfl.tc.error_e as e:
                    # FIXME: this is a hack, we need a proper timeout exception
                    if "NOT FOUND after" not in str(e):
                        raise
                    # if we timeout this one, just cancel it and go for the next
                    target.report_error(f"stopping after {timeout}s timeout")
                    # suspend -- Ctrl-Z & kill like really harshly
                    target.console_tx("\x1a\x1a\x1a")
                    target.expect("SIGTSTP")
                    target.shell.run(
                        f"podman kill --signal KILL {self.runid_hashid} || true"
                    )  # might be dez already

        # bring the log files home; SSH is way faster than over the console
        target.shell.run('tar cjf logs.tar.bz2 logs/')
        target.tunnel.ip_addr = target.addr_get(ic, "ipv4")
        target.ssh.copy_from("logs.tar.bz2", self.tmpdir)
        subprocess.check_output(["tar", "xf", "logs.tar.bz2"],
                                stderr=subprocess.STDOUT,
                                cwd=self.tmpdir)

        # cat each log file to tell what happened? we know the log
        # file names, so we can just iterate in python
        for testsuite in self.subcases:
            dirname = os.path.dirname(testsuite)
            name = os.path.basename(testsuite)
            result = tcfl.result_c()
            with self.subcase(testsuite), \
                 open(os.path.join(self.tmpdir, "logs", dirname, name + ".log")) as lf:
                target.report_info("parsing logs")
                try:
                    # FIXME: support multiple formats, for now hard coded to TAP
                    d = tcfl.tl.tap_parse_output(lf)
                except Exception as e:
                    d = dict()
                    result += tcfl.result_c.report_from_exception(self, e)
                for name, data in d.items():
                    _tc_name = commonl.name_make_safe(name)
                    with self.subcase(_tc_name):
                        self.subtc[tcfl.msgid_c.subcase()].update(
                            tcfl.tl.tap_mapping_result_c[data['result']],
                            data.get('subject', str(data['plan_count'])),
                            data['output'])
Ejemplo n.º 11
0
    def on(self, target, component):
        # Start QEMU
        #
        # We first assign a port for GDB debugging, if someone takes
        # it before we do, start will fail and paranoid mode (see
        # above) will restart it.
        # FIXME: allocate ports for VNC only if there is a vnc= in the
        # command line
        base = ttbl.config.tcp_port_range[0]
        top = ttbl.config.tcp_port_range[1]
        if base < 5900:  # we need ports >= 5900 for VNC
            base = 5900
        tcp_port_base = commonl.tcp_port_assigner(2, port_range=(base, top))
        target.fsdb.set("qemu-gdb-tcp-port", "%s" % tcp_port_base)
        # This might not be used at all, but we allocate and declare
        # it in case the implementation will use it; allocating in a
        # higher layer makes it more complicated.
        # this one is the port number based on 5900 (VNC default 0)
        if top < 5900:
            logging.warning(
                "ttbl.config.tcp_port_range %s doesn't include ports "
                "between above 5900, needed for VNC services. "
                "QEMU targets needing VNC support will fail to start "
                "complaining about 'vnc-port' not defined",
                ttbl.config.tcp_port_range)
        else:
            # FIXME: move to vnc.vnc0.{host,port,tcp-port}
            # set this for general information; the VNC screenshotter
            # also uses it
            target.fsdb.set("vnc-host", "localhost")
            target.fsdb.set("vnc-port", "%s" % (tcp_port_base + 1 - 5900))
            # this one is the raw port number
            target.fsdb.set("vnc-tcp-port", "%s" % (tcp_port_base + 1))
            # New form
            target.fsdb.set("vnc.vnc0.host", "localhost")
            target.fsdb.set("vnc.vnc0.port", "%s" % (tcp_port_base + 1 - 5900))
            # this one is the raw port number
            target.fsdb.set("vnc.vnc0.tcp_port", "%s" % (tcp_port_base + 1))

        self.cmdline_extra = []
        image_keys = target.fsdb.keys("qemu-image-*")
        #
        # Images interface: flash() below has been used to set images
        # to run, this will feed them into QEMU
        #
        if 'qemu-image-bios' in image_keys:
            self.cmdline_extra += ["-bios", "%(qemu-image-bios)s"]
        if 'qemu-image-kernel' in image_keys:
            self.cmdline_extra += ["-kernel", "%(qemu-image-kernel)s"]
        if 'qemu-image-kernel-args' in image_keys:
            self.cmdline_extra += ["-append", "%(qemu-image-kernel-args)s"]
        if 'qemu-image-initrd' in image_keys:
            self.cmdline_extra += ["-initrd", "%(qemu-image-initrd)s"]

        #
        # Network support bits--add command line options to connect
        # this VM to the networks the target declares
        #
        # For each network we are connected to, there must be a
        # network_tap_pc power rail controller that sets up the
        # network devices for us before starting QEMU.
        #
        # We look at it to setup the QEMU side of things as extra
        # command line options.
        #
        # - network name/icname: comes from the tuntap-ICNAME property
        #   created by the network_tap_pc power rail controller that
        #   the configuration must put on before the qemu power rail
        #   controller.
        #
        # - model: comes from what HW it has to emulate; each arch has a
        #   default or you can set for all with property qemu-model or
        #   qemu-model-ICNAME (for that specifc interconnect).
        #
        #   It has to be a valid QEMU model or QEMU will reject it
        #   with an error. You can find valid models with::
        #
        #     qemu-system-ARCH -net nic,model=help
        #
        # Zephyr, e.g., supports a few: lan9118 stellaris e1000 (from)::
        #
        #   $ grep --color -nH --null -rie ETH_NIC_MODEL
        #   drivers/ethernet/Kconfig.smsc911x:13:config ETH_NIC_MODEL$
        #   drivers/ethernet/Kconfig.stellaris:15:config ETH_NIC_MODEL$
        #   drivers/ethernet/Kconfig.e1000:16:config ETH_NIC_MODEL$
        for tap, if_name in target.fsdb.get_as_dict("tuntap-*").items():
            # @tap is tuntap-NETWORK, the property set by
            # powe rail component network_tap_pc.
            _, ic_name = tap.split("-", 1)
            mac_addr = target.tags['interconnects'][ic_name]['mac_addr']
            model = commonl.name_make_safe(
                target.fsdb.get("qemu-model-" + ic_name,
                                target.fsdb.get("qemu-model", self.nic_model)))
            self.cmdline_extra += [
                "-device",
                #
                # romfile= UNSET for using virtio-net
                #  - https://wiki.syslinux.org/wiki/index.php?title=Development/Testing
                #    Workaround using virtio
                f"{model},netdev={ic_name},mac={mac_addr},romfile=",
                "-netdev",
                f"tap,id={ic_name},script=no,ifname={if_name}"
            ]

        #
        # Ok, so do actually start
        #
        # if the debug port is snatched before we start by someone
        # else, this will fail and we'll have to retry--this is
        # why the power component is set to paranoid mode, so the
        # power interface automatically retries it if it fails.
        ttbl.power.daemon_c.on(self, target, component)

        # run console/on steps
        self._qemu_console_on(target, component)

        #
        # Debugging interface--if the target is not in debugging mode,
        # tell QEMU to start right away
        #
        if target.fsdb.get("debug") == None:
            self.debug_resume(target, component)
Ejemplo n.º 12
0
    def on(self, target, component):
        # Start QEMU
        #
        # We first assign a port for GDB debugging, if someone takes
        # it before we do, start will fail and paranoid mode (see
        # above) will restart it.
        base = ttbl.config.tcp_port_range[0]
        top = ttbl.config.tcp_port_range[1]
        if base < 5900:  # we need ports >= 5900 for VNC
            base = 5900
        tcp_port_base = commonl.tcp_port_assigner(2, port_range=(base, top))
        target.fsdb.set("qemu-gdb-tcp-port", "%s" % tcp_port_base)
        # This might not be used at all, but we allocate and declare
        # it in case the implementation will use it; allocating in a
        # higher layer makes it more complicated.
        # this one is the port number based on 5900 (VNC default 0)
        if top < 5900:
            logging.warning(
                "ttbl.config.tcp_port_range %s doesn't include ports "
                "between above 5900, needed for VNC services. "
                "QEMU targets needing VNC support will fail to start "
                "complaining about 'vnc-port' not defined",
                ttbl.config.tcp_port_range)
        else:
            # set this for general information; the VNC screenshotter
            # also uses it
            target.fsdb.set("vnc-host", "localhost")
            target.fsdb.set("vnc-port", "%s" % (tcp_port_base + 1 - 5900))
            # this one is the raw port number
            target.fsdb.set("vnc-tcp-port", "%s" % (tcp_port_base + 1))

        self.cmdline_extra = []
        image_keys = target.fsdb.keys("qemu-image-*")
        #
        # Images interface: flash() below has been used to set images
        # to run, this will feed them into QEMU
        #
        if 'qemu-image-bios' in image_keys:
            self.cmdline_extra += ["-bios", "%(qemu-image-bios)s"]
        if 'qemu-image-kernel' in image_keys:
            self.cmdline_extra += ["-kernel", "%(qemu-image-kernel)s"]
        if 'qemu-image-kernel-args' in image_keys:
            self.cmdline_extra += ["-append", "%(qemu-image-kernel-args)s"]
        if 'qemu-image-initrd' in image_keys:
            self.cmdline_extra += ["-initrd", "%(qemu-image-initrd)s"]

        #
        # Network support bits--add command line options to connect
        # this VM to the networks the target declares
        #
        # For each network we are connected to, there must be a
        # network_tap_pc power rail controller that sets up the
        # network devices for us before starting QEMU.
        #
        # We look at it to setup the QEMU side of things as extra
        # command line options.
        #
        # - network name/icname: comes from the tuntap-ICNAME property
        #   created by the network_tap_pc power rail controller that
        #   the configuration must put on before the qemu power rail
        #   controller.
        #
        # - model: comes from what HW it has to emulate; each arch has a
        #   default or you can set for all with property qemu-model or
        #   qemu-model-ICNAME (for that specifc interconnect).
        #
        #   It has to be a valid QEMU model or QEMU will reject it
        #   with an error. You can find valid models with::
        #
        #     qemu-system-ARCH -net nic,model=help
        #
        # Zephyr, e.g., supports a few: lan9118 stellaris e1000 (from)::
        #
        #   $ grep --color -nH --null -rie ETH_NIC_MODEL
        #   drivers/ethernet/Kconfig.smsc911x:13:config ETH_NIC_MODEL$
        #   drivers/ethernet/Kconfig.stellaris:15:config ETH_NIC_MODEL$
        #   drivers/ethernet/Kconfig.e1000:16:config ETH_NIC_MODEL$
        fds = []
        try:
            for tap, if_name in target.fsdb.get_as_dict(
                    "tuntap-*").iteritems():
                # @tap is tuntap-NETWORK, the property set by
                # powe rail component network_tap_pc.
                _, ic_name = tap.split("-", 1)
                mac_addr = target.tags['interconnects'][ic_name]['mac_addr']
                model = commonl.name_make_safe(
                    target.fsdb.get(
                        "qemu-model-" + ic_name,
                        target.fsdb.get("qemu-model", self.nic_model)))

                # There is no way to cahole QEMU to say: hey, we
                # already have a tap interface configured for you, use
                # it...other than to open it and pass the file
                # descriptor to it.
                # So this finds the /dev/tap* file which matches the
                # interface created with tuntap- (class
                # network_tap_pc), opens it and passes the descriptor
                # along to qemu, then closes the FD, since we don't
                # need it.
                tapindex = commonl.if_index(if_name)
                # Need to wait for udev to reconfigure this for us to have
                # access; this is done by a udev rule installed
                # (/usr/lib/udev/rules.d/80-ttbd.rules)
                tapdevname = "/dev/tap%d" % tapindex
                ts0 = time.time()
                ts = ts0
                timeout = 5
                fd = None
                while ts - ts0 < timeout:
                    try:
                        fd = os.open(tapdevname, os.O_RDWR, 0)
                        fds.append(fd)
                        break
                    except OSError as e:
                        target.log.error(
                            "%s: couldn't open %s after"
                            " +%.1f/%.1fs, retrying: %s", ic_name, tapdevname,
                            ts - ts0, timeout, e)
                        time.sleep(0.25)
                        ts = time.time()
                else:
                    raise RuntimeError(
                        "%s: timedout (%.1fs) opening /dev/tap%d's;"
                        " udev's /usr/lib/udev/rules.d/80-ttbd.rules"
                        " didn't set permissions?" %
                        (ic_name, timeout, tapindex))

                self.cmdline_extra += [
                    "-nic",
                    "tap,fd=%d,id=%s,model=%s,mac=%s,br=_b%s" \
                    % (fd, ic_name, model, mac_addr, ic_name )
                ]

            if not fds:
                # No tuntap network attachments -> no network
                # support. Cap it.
                self.cmdline_extra += ['-nic', 'none']

            #
            # Ok, so do actually start
            #
            # if the debug port is snatched before we start by someone
            # else, this will fail and we'll have to retry--this is
            # why the power component is set to paranoid mode, so the
            # power interface automatically retries it if it fails.
            ttbl.power.daemon_c.on(self, target, component)

            # Those consoles? update their generational counts so the
            # clients can know they restarted
            if hasattr(target, "console"):
                for console in target.console.impls.keys():
                    ttbl.console.generation_set(target, console)

        finally:
            for fd in fds:  # close fds we opened for ...
                os.close(fd)  # ... networking, unneeded now
        #
        # Debugging interface--if the target is not in debugging mode,
        # tell QEMU to start right away
        #
        if target.fsdb.get("debug") == None:
            self.debug_resume(target, component)
Ejemplo n.º 13
0
    def _eval_one(self, target, t_file, prefix):
        result = tcfl.tc.result_c(0, 0, 0, 0, 0)
        rel_file_path = os.path.join(prefix, t_file)
        if self._ts_ignore(rel_file_path):
            target.report_skip(
                "%s: skipped due to configuration "
                "(tcfl.tc_clear_bbt.ignore_ts or BBT_IGNORE_TS environment)"
                % rel_file_path)
            for name, subtc in self.subtcs.iteritems():
                if name.startswith(t_file):
                    subtc.result = tcfl.tc.result_c(0, 0, 0, 0, 1)
                    subtc.data = dict(result = "skipped")
            result.skipped += 1
        else:
            self.report_info("running %s%s" % (prefix, t_file))
            self.kw_set("t_file", t_file)
            output = target.shell.run(
                # remember we cd'ed into the directory, the way these
                # BBTs are written, it is expected
                "bats --tap %s || echo FAILED''-%s"
                % (t_file, self.kws['tc_hash']),
                output = True)
            # top level result
            if 'bats: command not found' in output:
                self.report_error(
                    "'bats' tool not installed in the target",
                    dict(target = target, output = output))
                result.errors += 1
            elif 'FAILED-%(tc_hash)s' % self.kws in output:
                result.failed += 1
            else:
                result.passed += 1

            # seems we had execution, so let's parse the output and
            # make subcases of the .t -- if anything fails, catch and
            # convert to a TCF exception so it only affects *this*
            # testcase in the result accounting--note
            # report_from_exception() will report exceptio data so we
            # can debug if it is an infra or TC problem.
            try:
                tcs = tap_parse_output(output)
            except Exception as e:
                tcs = dict()
                result += tcfl.tc.result_c.report_from_exception(self, e)
            for name, data in tcs.iteritems():
                # get the subtc; see _scan_t_subcases() were we keyed
                # them in
                _name = commonl.name_make_safe(name.strip()).rstrip("_")
                tc_name = t_file + "." + _name
                subtc = self.subtcs[tc_name]
                if self._ts_ignore(subtc.name):
                    data['result'] += \
                        "result skipped due to configuration " \
                        "(tcfl.tc_clear_bbt.ignore_ts or " \
                        "BBT_IGNORE_TS environment)"
                    subtc.update(tcfl.tc.result_c(0, 0, 0, 0, 1), data)
                else:
                    # translate the taps result to a TCF result, record it
                    subtc.update(self.mapping[data['result']], data)
                result += subtc.result
        return result