utils.py 20.7 KB
Newer Older
W
wenjun 已提交
1 2 3 4
#!/usr/bin/env python3
# coding=utf-8

#
M
mamingshuai 已提交
5
# Copyright (c) 2020-2021 Huawei Device Co., Ltd.
W
wenjun 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

M
mamingshuai 已提交
19
import copy
W
wenjun 已提交
20 21 22 23 24 25 26 27 28
import os
import socket
import time
import platform
import argparse
import subprocess
import signal
import uuid
import json
M
mamingshuai 已提交
29
import stat
W
wenjun 已提交
30 31 32 33 34
from tempfile import NamedTemporaryFile

from _core.executor.listener import SuiteResult
from _core.driver.parser_lite import ShellHandler
from _core.exception import ParamError
M
mamingshuai 已提交
35
from _core.exception import ExecuteTerminate
W
wenjun 已提交
36 37 38 39
from _core.logger import platform_logger
from _core.report.suite_reporter import SuiteReporter
from _core.plugin import get_plugin
from _core.plugin import Plugin
M
mamingshuai 已提交
40 41
from _core.constants import ModeType
from _core.constants import ConfigConst
W
wenjun 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

LOG = platform_logger("Utils")


def get_filename_extension(file_path):
    _, fullname = os.path.split(file_path)
    filename, ext = os.path.splitext(fullname)
    return filename, ext


def unique_id(type_name, value):
    return "{}_{}_{:0>8}".format(type_name, value,
                                 str(uuid.uuid1()).split("-")[0])


def start_standing_subprocess(cmd, pipe=subprocess.PIPE, return_result=False):
    """Starts a non-blocking subprocess that is going to continue running after
    this function returns.

    A subprocess group is actually started by setting sid, so we can kill all
    the processes spun out from the subprocess when stopping it. This is
    necessary in case users pass in pipe commands.

    Args:
        cmd: Command to start the subprocess with.
        pipe: pipe to get execution result
        return_result: return execution result or not

    Returns:
        The subprocess that got started.
    """
    sys_type = platform.system()
    process = subprocess.Popen(cmd, stdout=pipe, shell=False,
                               preexec_fn=None if sys_type == "Windows"
                               else os.setsid)
    if not return_result:
        return process
    else:
        rev = process.stdout.read()
        return rev.decode("utf-8").strip()


def stop_standing_subprocess(process):
    """Stops a subprocess started by start_standing_subprocess.

    Catches and ignores the PermissionError which only happens on Macs.

    Args:
        process: Subprocess to terminate.
    """
    try:
        sys_type = platform.system()
        signal_value = signal.SIGINT if sys_type == "Windows" \
            else signal.SIGTERM
        os.kill(process.pid, signal_value)
    except (PermissionError, AttributeError, FileNotFoundError,
            SystemError) as error:
        LOG.error("stop standing subprocess error '%s'" % error)


def get_decode(stream):
M
mamingshuai 已提交
103 104 105 106 107 108 109 110 111
    if isinstance(stream, str):
        return stream

    if not isinstance(stream, bytes):
        return str(stream)

    try:
        ret = stream.decode("utf-8", errors="ignore")
    except (ValueError, AttributeError, TypeError):
W
wenjun 已提交
112 113 114 115 116 117
        ret = str(stream)
    return ret


def is_proc_running(pid, name=None):
    if platform.system() == "Windows":
M
mamingshuai 已提交
118 119
        list_command = ["C:\\Windows\\System32\\tasklist"]
        find_command = ["C:\\Windows\\System32\\findstr", "%s" % pid]
W
wenjun 已提交
120
    else:
M
mamingshuai 已提交
121 122 123
        list_command = ["/bin/ps", "-ef"]
        find_command = ["/bin/grep", "%s" % pid]
    proc = _get_find_proc(find_command, list_command)
W
wenjun 已提交
124 125 126 127 128 129 130 131
    (out, _) = proc.communicate()
    out = get_decode(out).strip()
    if out == "":
        return False
    else:
        return True if name is None else out.find(name) != -1


M
mamingshuai 已提交
132 133 134 135 136 137 138 139
def _get_find_proc(find_command, list_command):
    proc_sub = subprocess.Popen(list_command, stdout=subprocess.PIPE,
                                shell=False)
    proc = subprocess.Popen(find_command, stdin=proc_sub.stdout,
                            stdout=subprocess.PIPE, shell=False)
    return proc


A
alex__hold 已提交
140
def exec_cmd(cmd, timeout=1 * 60, error_print=True, join_result=False):
W
wenjun 已提交
141 142 143 144 145 146 147 148 149 150
    """
    Executes commands in a new shell. Directing stderr to PIPE.

    This is fastboot's own exe_cmd because of its peculiar way of writing
    non-error info to stderr.

    Args:
        cmd: A sequence of commands and arguments.
        timeout: timeout for exe cmd.
        error_print: print error output or not.
M
mamingshuai 已提交
151
        join_result: join error and out
W
wenjun 已提交
152 153 154 155 156
    Returns:
        The output of the command run.
    """

    sys_type = platform.system()
A
alex__hold 已提交
157 158 159 160
    if isinstance(cmd, list):
        LOG.info("The running command is: {}".format(" ".join(cmd)))
    if isinstance(cmd, str):
        LOG.info("The running command is: {}".format(cmd))
M
mamingshuai 已提交
161
    if sys_type == "Linux" or sys_type == "Darwin":
W
wenjun 已提交
162 163
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, shell=False,
M
mamingshuai 已提交
164
                                preexec_fn=os.setsid)
W
wenjun 已提交
165
    else:
M
mamingshuai 已提交
166 167
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, shell=False)
W
wenjun 已提交
168 169 170 171 172
    try:
        (out, err) = proc.communicate(timeout=timeout)
        err = get_decode(err).strip()
        out = get_decode(out).strip()
        if err and error_print:
M
mamingshuai 已提交
173 174 175 176 177
            LOG.exception(err, exc_info=False)
        if join_result:
            return "%s\n %s" % (out, err) if err else out
        else:
            return err if err else out
W
wenjun 已提交
178

M
mamingshuai 已提交
179
    except (TimeoutError, KeyboardInterrupt, AttributeError, ValueError,
A
alex__hold 已提交
180
            EOFError, IOError, subprocess.TimeoutExpired):
W
wenjun 已提交
181
        sys_type = platform.system()
M
mamingshuai 已提交
182
        if sys_type == "Linux" or sys_type == "Darwin":
W
wenjun 已提交
183
            os.killpg(proc.pid, signal.SIGTERM)
M
mamingshuai 已提交
184 185 186
        else:
            os.kill(proc.pid, signal.SIGINT)
        raise
W
wenjun 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200


def create_dir(path):
    """Creates a directory if it does not exist already.

    Args:
        path: The path of the directory to create.
    """
    full_path = os.path.abspath(os.path.expanduser(path))
    if not os.path.exists(full_path):
        os.makedirs(full_path, exist_ok=True)


def get_config_value(key, config_dict, is_list=True, default=None):
M
mamingshuai 已提交
201 202 203 204 205 206 207 208 209 210 211
    """get corresponding values for key in config_dict

    Args:
        key: target key in config_dict
        config_dict: dictionary that store values
        is_list: decide return values is list type or not
        default: if key not in config_dict, default value will be returned

    Returns:
        corresponding values for key
    """
W
wenjun 已提交
212 213 214
    if not isinstance(config_dict, dict):
        return default

M
mamingshuai 已提交
215
    value = config_dict.get(key, None)
W
wenjun 已提交
216 217 218
    if isinstance(value, bool):
        return value

M
mamingshuai 已提交
219 220
    if value is None:
        if default is not None:
W
wenjun 已提交
221 222 223 224 225 226 227 228 229
            return default
        return [] if is_list else ""

    if isinstance(value, list):
        return value if is_list else value[0]
    return [value] if is_list else value


def get_file_absolute_path(input_name, paths=None, alt_dir=None):
M
mamingshuai 已提交
230 231 232 233 234 235 236 237 238 239 240
    """find absolute path for input_name

    Args:
        input_name: the target file to search
        paths: path list for searching input_name
        alt_dir: extra dir that appended to paths

    Returns:
        absolute path for input_name
    """
    input_name = str(input_name)
W
wenjun 已提交
241 242 243
    abs_paths = set(paths) if paths else set()
    _update_paths(abs_paths)

M
mamingshuai 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257
    _inputs = [input_name]
    if input_name.startswith("resource/"):
        _inputs.append(input_name.replace("resource/", "", 1))
    elif input_name.startswith("testcases/"):
        _inputs.append(input_name.replace("testcases/", "", 1))

    for _input in _inputs:
        for path in abs_paths:
            if alt_dir:
                file_path = os.path.join(path, alt_dir, _input)
                if os.path.exists(file_path):
                    return os.path.abspath(file_path)

            file_path = os.path.join(path, _input)
W
wenjun 已提交
258 259 260 261
            if os.path.exists(file_path):
                return os.path.abspath(file_path)

    err_msg = "The file {} does not exist".format(input_name)
M
mamingshuai 已提交
262 263 264
    if check_mode(ModeType.decc):
        LOG.error(err_msg, error_no="00109")
        err_msg = "Load Error[00109]"
W
wenjun 已提交
265 266 267 268 269 270

    if alt_dir:
        LOG.debug("alt_dir is %s" % alt_dir)
    LOG.debug("paths is:")
    for path in abs_paths:
        LOG.debug(path)
M
mamingshuai 已提交
271
    raise ParamError(err_msg, error_no="00109")
W
wenjun 已提交
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316


def _update_paths(paths):
    from xdevice import Variables
    resource_dir = "resource"
    testcases_dir = "testcases"

    need_add_path = set()
    for path in paths:
        if not os.path.exists(path):
            continue
        head, tail = os.path.split(path)
        if not tail:
            head, tail = os.path.split(head)
        if tail in [resource_dir, testcases_dir]:
            need_add_path.add(head)
    paths.update(need_add_path)

    inner_dir = os.path.abspath(os.path.join(Variables.exec_dir,
                                             testcases_dir))
    top_inner_dir = os.path.abspath(os.path.join(Variables.top_dir,
                                                 testcases_dir))
    res_dir = os.path.abspath(os.path.join(Variables.exec_dir, resource_dir))
    top_res_dir = os.path.abspath(os.path.join(Variables.top_dir,
                                               resource_dir))
    paths.update([inner_dir, res_dir, top_inner_dir, top_res_dir,
                  Variables.exec_dir, Variables.top_dir])


def modify_props(device, local_prop_file, target_prop_file, new_props):
    """To change the props if need
    Args:
        device: the device to modify props
        local_prop_file : the local file to save the old props
        target_prop_file : the target prop file to change
        new_props  : the new props
    Returns:
        True : prop file changed
        False : prop file no need to change
    """
    is_changed = False
    device.pull_file(target_prop_file, local_prop_file)
    old_props = {}
    changed_prop_key = []
    lines = []
M
mamingshuai 已提交
317 318 319
    flags = os.O_RDONLY
    modes = stat.S_IWUSR | stat.S_IRUSR
    with os.fdopen(os.open(local_prop_file, flags, modes), "r") as old_file:
W
wenjun 已提交
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        lines = old_file.readlines()
        if lines:
            lines[-1] = lines[-1] + '\n'
        for line in lines:
            line = line.strip()
            if not line.startswith("#") and line.find("=") > 0:
                key_value = line.split("=")
                if len(key_value) == 2:
                    old_props[line.split("=")[0]] = line.split("=")[1]

    for key, value in new_props.items():
        if key not in old_props.keys():
            lines.append("".join([key, "=", value, '\n']))
            is_changed = True
        elif old_props.get(key) != value:
            changed_prop_key.append(key)
            is_changed = True

    if is_changed:
        local_temp_prop_file = NamedTemporaryFile(mode='w', prefix='build',
                                                  suffix='.tmp', delete=False)
        for index, line in enumerate(lines):
            if not line.startswith("#") and line.find("=") > 0:
                key = line.split("=")[0]
                if key in changed_prop_key:
                    lines[index] = "".join([key, "=", new_props[key], '\n'])
        local_temp_prop_file.writelines(lines)
        local_temp_prop_file.close()
        device.push_file(local_temp_prop_file.name, target_prop_file)
        device.execute_shell_command(" ".join(["chmod 644", target_prop_file]))
        LOG.info("Changed the system property as required successfully")
        os.remove(local_temp_prop_file.name)

    return is_changed


M
mamingshuai 已提交
356 357
def get_device_log_file(report_path, serial=None, log_name="device_log",
                        device_name=""):
W
wenjun 已提交
358 359 360 361 362
    from xdevice import Variables
    log_path = os.path.join(report_path, Variables.report_vars.log_dir)
    os.makedirs(log_path, exist_ok=True)

    serial = serial or time.time_ns()
M
mamingshuai 已提交
363 364 365 366
    if device_name:
        serial = "%s_%s" % (device_name, serial)
    device_file_name = "{}_{}.log".format(log_name, str(serial).replace(
        ":", "_"))
W
wenjun 已提交
367
    device_log_file = os.path.join(log_path, device_file_name)
M
mamingshuai 已提交
368
    LOG.info("generate device log file: %s", device_log_file)
W
wenjun 已提交
369 370 371
    return device_log_file


M
mamingshuai 已提交
372 373 374 375 376 377 378
def check_result_report(report_root_dir, report_file, error_message="",
                        report_name="", module_name=""):
    """
    check whether report_file exits or not. if report_file is not exist,
    create empty report with error_message under report_root_dir
    """

W
wenjun 已提交
379 380
    if os.path.exists(report_file):
        return report_file
M
mamingshuai 已提交
381 382 383 384 385
    report_dir = os.path.dirname(report_file)
    if os.path.isabs(report_dir):
        result_dir = report_dir
    else:
        result_dir = os.path.join(report_root_dir, "result", report_dir)
W
wenjun 已提交
386
    os.makedirs(result_dir, exist_ok=True)
M
mamingshuai 已提交
387 388 389 390 391
    if check_mode(ModeType.decc):
        LOG.error("report not exist, create empty report")
    else:
        LOG.error("report %s not exist, create empty report under %s" % (
            report_file, result_dir))
W
wenjun 已提交
392 393 394 395 396 397 398

    suite_name = report_name
    if not suite_name:
        suite_name, _ = get_filename_extension(report_file)
    suite_result = SuiteResult()
    suite_result.suite_name = suite_name
    suite_result.stacktrace = error_message
M
mamingshuai 已提交
399 400
    if module_name:
        suite_name = module_name
W
wenjun 已提交
401
    suite_reporter = SuiteReporter([(suite_result, [])], suite_name,
M
mamingshuai 已提交
402
                                   result_dir, modulename=module_name)
W
wenjun 已提交
403 404 405 406
    suite_reporter.create_empty_report()
    return "%s.xml" % os.path.join(result_dir, suite_name)


M
mamingshuai 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419 420
def get_sub_path(test_suite_path):
    pattern = "%stests%s" % (os.sep, os.sep)
    file_dir = os.path.dirname(test_suite_path)
    pos = file_dir.find(pattern)
    if -1 == pos:
        return ""

    sub_path = file_dir[pos + len(pattern):]
    pos = sub_path.find(os.sep)
    if -1 == pos:
        return ""
    return sub_path[pos + len(os.sep):]


W
wenjun 已提交
421 422 423 424 425 426 427 428 429 430
def is_config_str(content):
    return True if "{" in content and "}" in content else False


def get_version():
    from xdevice import Variables
    ver = ''
    ver_file_path = os.path.join(Variables.res_dir, 'version.txt')
    if not os.path.isfile(ver_file_path):
        return ver
M
mamingshuai 已提交
431 432 433
    flags = os.O_RDONLY
    modes = stat.S_IWUSR | stat.S_IRUSR
    with os.fdopen(os.open(ver_file_path, flags, modes), "r") as ver_file:
W
wenjun 已提交
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        line = ver_file.readline()
        if '-v' in line:
            ver = line.strip().split('-')[1]
            ver = ver.split(':')[0]

    return ver


def get_instance_name(instance):
    return instance.__class__.__name__


def convert_ip(origin_ip):
    addr = origin_ip.strip().split(".")
    if len(addr) == 4:
        return "{}.{}.{}.{}".format(
            addr[0], '*'*len(addr[1]), '*'*len(addr[2]), addr[-1])
    else:
        return origin_ip


def convert_port(port):
M
mamingshuai 已提交
456 457 458
    _port = str(port)
    if len(_port) >= 2:
        return "{}{}{}".format(_port[0], "*" * (len(_port) - 2), _port[-1])
W
wenjun 已提交
459
    else:
M
mamingshuai 已提交
460
        return "*{}".format(_port[-1])
W
wenjun 已提交
461 462 463 464


def convert_serial(serial):
    if serial.startswith("local_"):
M
mamingshuai 已提交
465
        return serial
W
wenjun 已提交
466
    elif serial.startswith("remote_"):
M
mamingshuai 已提交
467 468
        return "remote_{}_{}".format(convert_ip(serial.split("_")[1]),
                                     convert_port(serial.split("_")[-1]))
W
wenjun 已提交
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    else:
        length = len(serial)//3
        return "{}{}{}".format(
            serial[0:length], "*"*(len(serial)-length*2), serial[-length:])


def get_shell_handler(request, parser_type):
    suite_name = request.root.source.test_name
    parsers = get_plugin(Plugin.PARSER, parser_type)
    parser_instances = []
    for listener in request.listeners:
        listener.device_sn = request.config.environment.devices[0].device_sn
    for parser in parsers:
        parser_instance = parser.__class__()
        parser_instance.suite_name = suite_name
        parser_instance.listeners = request.listeners
        parser_instances.append(parser_instance)
    handler = ShellHandler(parser_instances)
    return handler


M
mamingshuai 已提交
490
def get_kit_instances(json_config, resource_path="", testcases_path=""):
W
wenjun 已提交
491 492
    from _core.testkit.json_parser import JsonParser
    kit_instances = []
M
mamingshuai 已提交
493 494

    # check input param
W
wenjun 已提交
495 496
    if not isinstance(json_config, JsonParser):
        return kit_instances
M
mamingshuai 已提交
497 498

    # get kit instances
W
wenjun 已提交
499 500 501
    for kit in json_config.config.kits:
        kit["paths"] = [resource_path, testcases_path]
        kit_type = kit.get("type", "")
M
mamingshuai 已提交
502
        device_name = kit.get("device_name", None)
W
wenjun 已提交
503 504 505 506 507
        if get_plugin(plugin_type=Plugin.TEST_KIT, plugin_id=kit_type):
            test_kit = \
                get_plugin(plugin_type=Plugin.TEST_KIT, plugin_id=kit_type)[0]
            test_kit_instance = test_kit.__class__()
            test_kit_instance.__check_config__(kit)
M
mamingshuai 已提交
508
            setattr(test_kit_instance, "device_name", device_name)
W
wenjun 已提交
509 510
            kit_instances.append(test_kit_instance)
        else:
M
mamingshuai 已提交
511
            raise ParamError("kit %s not exists" % kit_type, error_no="00107")
W
wenjun 已提交
512 513 514
    return kit_instances


M
mamingshuai 已提交
515 516 517 518 519 520 521 522 523 524 525 526 527 528
def check_device_name(device, kit, step="setup"):
    kit_device_name = getattr(kit, "device_name", None)
    device_name = device.get("name")
    if kit_device_name and device_name and \
            kit_device_name != device_name:
        return False
    if kit_device_name and device_name:
        LOG.debug("do kit:%s %s for device:%s",
                  kit.__class__.__name__, step, device_name)
    else:
        LOG.debug("do kit:%s %s", kit.__class__.__name__, step)
    return True


W
wenjun 已提交
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
def check_path_legal(path):
    if path and " " in path:
        return "\"%s\"" % path
    return path


def get_local_ip():
    sys_type = platform.system()
    if sys_type == "Windows":
        _list = socket.gethostbyname_ex(socket.gethostname())
        _list = _list[2]
        for ip_add in _list:
            if ip_add.startswith("10."):
                return ip_add

        return socket.gethostbyname(socket.getfqdn(socket.gethostname()))
    elif sys_type == "Darwin":
        hostname = socket.getfqdn(socket.gethostname())
        return socket.gethostbyname(hostname)
    elif sys_type == "Linux":
        real_ip = "/%s/%s" % ("hostip", "realip")
        if os.path.exists(real_ip):
            srw = None
            try:
                import codecs
                srw = codecs.open(real_ip, "r", "utf-8")
                lines = srw.readlines()
                local_ip = str(lines[0]).strip()
            except (IOError, ValueError) as error_message:
                LOG.error(error_message)
                local_ip = "127.0.0.1"
            finally:
                if srw is not None:
                    srw.close()
        else:
            local_ip = "127.0.0.1"
        return local_ip
M
mamingshuai 已提交
566 567
    else:
        return "127.0.0.1"
W
wenjun 已提交
568 569 570 571 572 573 574 575


class SplicingAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, " ".join(values))


def get_test_component_version(config):
M
mamingshuai 已提交
576 577 578
    if check_mode(ModeType.decc):
        return ""

W
wenjun 已提交
579 580 581
    try:
        paths = [config.resource_path, config.testcases_path]
        test_file = get_file_absolute_path("test_component.json", paths)
M
mamingshuai 已提交
582 583 584
        flags = os.O_RDONLY
        modes = stat.S_IWUSR | stat.S_IRUSR
        with os.fdopen(os.open(test_file, flags, modes), "r") as file_content:
W
wenjun 已提交
585 586 587 588
            json_content = json.load(file_content)
            version = json_content.get("version", "")
            return version
    except (ParamError, ValueError) as error:
M
mamingshuai 已提交
589
        LOG.error("The exception {} happened when get version".format(error))
W
wenjun 已提交
590
    return ""
M
mamingshuai 已提交
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627


def check_mode(mode):
    from xdevice import Scheduler
    return Scheduler.mode == mode


def do_module_kit_setup(request, kits):
    for device in request.get_devices():
        setattr(device, ConfigConst.module_kits, [])

    from xdevice import Scheduler
    for kit in kits:
        run_flag = False
        for device in request.get_devices():
            if not Scheduler.is_execute:
                raise ExecuteTerminate()
            if check_device_name(device, kit):
                run_flag = True
                kit_copy = copy.deepcopy(kit)
                module_kits = getattr(device, ConfigConst.module_kits)
                module_kits.append(kit_copy)
                kit_copy.__setup__(device, request=request)
        if not run_flag:
            kit_device_name = getattr(kit, "device_name", None)
            error_msg = "device name '%s' of '%s' not exist" % (
                kit_device_name, kit.__class__.__name__)
            LOG.error(error_msg, error_no="00108")
            raise ParamError(error_msg, error_no="00108")


def do_module_kit_teardown(request):
    for device in request.get_devices():
        for kit in getattr(device, ConfigConst.module_kits, []):
            if check_device_name(device, kit, step="teardown"):
                kit.__teardown__(device)
        setattr(device, ConfigConst.module_kits, [])