From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 5C88DA0548; Thu, 8 Sep 2022 11:53:30 +0200 (CEST) Received: from [217.70.189.124] (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id EA46540143; Thu, 8 Sep 2022 11:53:29 +0200 (CEST) Received: from mga06.intel.com (mga06b.intel.com [134.134.136.31]) by mails.dpdk.org (Postfix) with ESMTP id 47F22400D6 for ; Thu, 8 Sep 2022 11:53:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1662630808; x=1694166808; h=date:from:to:cc:subject:message-id:references: content-transfer-encoding:in-reply-to:mime-version; bh=CxNn17ShSwRWUeKsHITLlWR/tGBrcxUXgI2TGRf9JdA=; b=gH+U0CGmbqOwnM6c5XWEhX/0hsq1r3M5O9gulLRuBZemPTLEOtqwuene oMWVI76Id3vI0aQv2w1+C4CPAqAHlGEp00D4d6C3tJw+htglt7HNYbUpo GKUE1e5SJgpmuvRHNzt/DTIN67PG0kVFekbZyEkJ9qHfHPnpoXD9OW5Zy L18+z3jvYIu4T+eRZHQl8xqffpN7pnVqEuzaaLfTwvsXbRPrbGCDGjWQd gsMdThYE4Flmk6REwSelRMdKRkzdG4KbAKTGFOx9nCHkt9z54K2HjrPPu 9paeuLmWZEJuB8ugLecUQdpIIbf+OoajbVIXZr/hLB2Id0PVIfL84s81Y g==; X-IronPort-AV: E=McAfee;i="6500,9779,10463"; a="358856156" X-IronPort-AV: E=Sophos;i="5.93,299,1654585200"; d="scan'208";a="358856156" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Sep 2022 02:53:26 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.93,299,1654585200"; d="scan'208";a="943277924" Received: from orsmsx603.amr.corp.intel.com ([10.22.229.16]) by fmsmga005.fm.intel.com with ESMTP; 08 Sep 2022 02:53:25 -0700 Received: from orsmsx607.amr.corp.intel.com (10.22.229.20) by ORSMSX603.amr.corp.intel.com (10.22.229.16) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Thu, 8 Sep 2022 02:53:25 -0700 Received: from orsedg603.ED.cps.intel.com (10.7.248.4) by orsmsx607.amr.corp.intel.com (10.22.229.20) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31 via Frontend Transport; Thu, 8 Sep 2022 02:53:25 -0700 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (104.47.55.169) by edgegateway.intel.com (134.134.137.100) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2375.31; Thu, 8 Sep 2022 02:53:24 -0700 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=QmI/W1/UT2l5/tOpMwY+siEFdCUeCzgYnt+UbJX3orMpTb4RwZySyYm70VEv/mJ6wUTTTLVlCL2ZPMaboR+6CTqAUxdA6NZr4fKyAAEEwjB+Ey9156/p7DNcUEgt9nDz0DrKRtzmrYceWy9fEme2rqSOnk+Hb4bE94JXVwQPIdfQKdD3T/bSlfIK5aFAZPYxvnJ6jqYEy85Pq2dpUr0oMpqCBEnGilFsxUecO6y7nUatdxDQ1BCajx2vk6/wnR59oeM9Wsd7GTux27vtwfRgr8JEpc9VY4PAtd16hRLppDvEeuHpNjbIabio6lCiRPLEm1Fn6SRdav7bQzvitz/PEA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=8gvhE9aPhM1A56JFrvkII8a8mrp8Wd8ThTVZe0+gE2c=; b=l4Xs+S01I7n+pgwK/minn11Qr4nbqhFUsFQWGL5/Wngg533I9jPbXWf++1fwUcgwHh7shKpbqqItVx8iIF6Ly9Iyc/jTZkqCQZjpvrIs7olaMpiWY+AKvbpXJfs393ADk6GAXlJx8d8jc7/DOuyN6vAxb3+3DikJQ8cwvzn3nLXvErNqpfTXivG3u4iaRP74HT4ZIQnIqxR0TAjQNejeO3o7XBKz8rdtOKhA3NMUSEiXqJb8Q94NP/csYVlBNpxYUlrYXtM0rBz26vTpSe/1cL8kz5pxqGYWvG8jMB+e7qFVGY2iBdcSoU6lfRa53LTliCg7HAFUaq9b5cOJrZ5fgA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=intel.com; dmarc=pass action=none header.from=intel.com; dkim=pass header.d=intel.com; arc=none Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=intel.com; Received: from MWHPR11MB1629.namprd11.prod.outlook.com (2603:10b6:301:d::21) by MWHPR11MB0013.namprd11.prod.outlook.com (2603:10b6:301:67::21) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5588.14; Thu, 8 Sep 2022 09:53:08 +0000 Received: from MWHPR11MB1629.namprd11.prod.outlook.com ([fe80::13c:8120:d994:16d2]) by MWHPR11MB1629.namprd11.prod.outlook.com ([fe80::13c:8120:d994:16d2%6]) with mapi id 15.20.5612.014; Thu, 8 Sep 2022 09:53:08 +0000 Date: Thu, 8 Sep 2022 10:53:01 +0100 From: Bruce Richardson To: Juraj =?utf-8?Q?Linke=C5=A1?= CC: , , , , , , Subject: Re: [PATCH v4 4/9] dts: add ssh pexpect library Message-ID: References: <20220728100044.1318484-1-juraj.linkes@pantheon.tech> <20220729105550.1382664-1-juraj.linkes@pantheon.tech> <20220729105550.1382664-5-juraj.linkes@pantheon.tech> Content-Type: text/plain; charset="utf-8" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <20220729105550.1382664-5-juraj.linkes@pantheon.tech> X-ClientProxiedBy: PA7P264CA0012.FRAP264.PROD.OUTLOOK.COM (2603:10a6:102:2d3::13) To MWHPR11MB1629.namprd11.prod.outlook.com (2603:10b6:301:d::21) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: MWHPR11MB1629:EE_|MWHPR11MB0013:EE_ X-MS-Office365-Filtering-Correlation-Id: 82f562ef-9f78-4668-f09f-08da917feec9 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: AWioKlN6+1+laFiRo7A7WksxO0mAp7kQlp8G51olzWDbub1erkuhw8KfWG2TQZIlst9JyxLatlVOYUfr2HYDRooS9dPzTt+rmgizbZQN2TgxjYbBsSjwVTqmf0rIWiZaAMa8QVaRMW8Hl5f03c3kRTNxCVi2L3iDnlLALF+XyNBeK+fKbxSpm/yWx9oTWPygdcJsoDlIjs98OE14LjSrkyDrQPd/B2qORfHxeO1/1UGuxKIb/v73u2BZ1WmIQO3+xLuNX1Xq0m8K6P9+BwWzKE7ZYnnVEucJZQEATih+dA3rUhkrtcSV7usFlc675EQsXzLdz14aClR5QvUS2MK1JHlfaYPQipq2QD3PdkK613YI7vx+mpl89zvDi6xwRc2arJ9lZLS85ATEOGrrJal38xcY3ctuZBFsInerIOWuExcwTmZh2i7A46OZygBIuCk4Mb+2q5TSYHZsXPIv1bcYiUuhGQRoThe4lSw/3SoZV94nw+78edymrlm6flXJU2quh0G8BjA4vzqBEjpqiwjO0Ok4NrxbceTO7wFx8Nx75N6O8VjMzSWlwhbQOaPwH0RfT5ssEEipiJ/YSlVBiWSMqNaH09KpPNXNF4aq01g2XHztK5mGYlz/xENWG7pkB7KOC3w4zFalYkzxvtOOo49U+skjfAlWSH8qkOEmC9qmRhgQs/YMioPnO9EMNV/mALer5UEczJHBRK17I5aPIA+jPg== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:MWHPR11MB1629.namprd11.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230016)(396003)(376002)(136003)(346002)(39860400002)(366004)(41300700001)(186003)(44832011)(6916009)(6486002)(478600001)(316002)(86362001)(82960400001)(6512007)(6506007)(38100700002)(26005)(8676002)(4326008)(6666004)(66946007)(66556008)(8936002)(83380400001)(66476007)(5660300002)(2906002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?d3Q0WC9ZTnk5QUJpTXV6WDd6RWdnZEpzY3lSSVBkaWlnUi80cHgwaVNjOHo0?= =?utf-8?B?eXhnbDRRU0l0VUw2bkJNSTVKS0RrM2JBZVhjQkRUeXJ1NFhGWFF5T1ROOWZM?= =?utf-8?B?SlNkbEVYYUpEVzd6Mlc3di9IanlweDllKzMzT2ZTc2NYWUdDOTdXYmIvUlVk?= =?utf-8?B?S0p4czluL2o4SGtHTklHV2JIZjJRK2NnejdCZUdOeFg5ZGxHd0tYVGkxVy9T?= =?utf-8?B?K2wwVmpvVG5Db1ExYTV6SXRiaDFHWFAzNlZRakUwNXNsNUJoenZTVTFBL05X?= =?utf-8?B?Y015NEV0N252UTFBRCtmcExzeU5aaDhBODNBSUNNdGMwclE0RS9sSEt2dFJu?= =?utf-8?B?OG5qWGtnOGh1dzhQOXJyRUU4VUNTcjVPR0swMnNkZkVwa0RXbzVnNDViZ1NS?= =?utf-8?B?eVppYkpZL2hIVnlhS05nN08vTmRWdTN0ZjFLWVpOY21GalZuU2N0L05aK0hP?= =?utf-8?B?Z25zUVZ6OTU4eW1ERmtJZXlmTG5hNjB3NEhpektSek44LzF5K2cwcGdBQzVS?= =?utf-8?B?QTVOVE9adnlJN1lCcVBORk5aNHlqd04wSldodVpTZThXZkRlUEJOOUNPWHNS?= =?utf-8?B?WWdMdE5UL3J6UTliK2JMVHBob1VVczBYQ2VKeGV1VXFPcm84SVA1V3FSVUR0?= =?utf-8?B?ajlQK1dXNEp2aVk3ZUZMSWZtNFlsL0dBVTFEQmZ0VnZuQ1Q5SXFIVGZ4eGdD?= =?utf-8?B?ZEl2bjhLZkNyY2h3RzFmQUxvODJlemZ2aEcwUDFqSnlOdGJDVVZQZ1RZQkdX?= =?utf-8?B?Z1RKQWJVT0xlRnZXZUl5Vnhya000aWsyUG56VFJLN2ZoRkNMWm1jcytrOHla?= =?utf-8?B?ZEIyYWpvZHpZSjFPNmVIMW9JYmZCV1lFM21sS0NheDh0SnNlNnppL2tKUTdT?= =?utf-8?B?WjlJRGd1QlZzZkZHMEx3cDZNSkNTWWc5a3FiZTVKdXFINDZ2QWpzRkdRRWJ0?= =?utf-8?B?dmQ3Ti9Da2lTL0M5VUg1ekFDQTBnL2p6bGJUYndiOWgvVU1hQ2RPYTUzVlhO?= =?utf-8?B?anZnYjZlYjd5US9Cb2M4Mk0xdFIrbWZGWUFhVVZxMy8wZ3RKUDgxb3RuSnVC?= =?utf-8?B?azVPdk4raVZYSHRpZUZKWkRxb0hQeHV0SmFiMENrM214T2g2SGI0UENFRC8x?= =?utf-8?B?eGVlMkYyNGJVY25NSkxxQTQvcUhwNDVjVzFIRHpiUWdoM1NTNnA3VTdhRXM0?= =?utf-8?B?TzVJYm0yek10VWxvTUNlTThmd1E5WWdpMUlseFF3V2N5MTAya2FoRE96QmV2?= =?utf-8?B?dVFDOTZLeFlXdEloWFhPUlpzaGJvU0VaMDZtNzFsOUF6M05uOVV3eG1xOGor?= =?utf-8?B?V21PMFdDREV0VDdXM2w5SERXSHczN24rUVBjMVE0MlYwVTZSeUZZdmlEYnh5?= =?utf-8?B?enNVUS9FbGpIMnE0Z3Nwa1hzaXJUMlNGN2NGd2hLRS9WcXJLMXlnZkJIaU5X?= =?utf-8?B?azFGZHk0aElYdlptM2w1VkdqZkw2UlFIZnZUK3E1Zm5RbHdJNFhvZU1lUUpD?= =?utf-8?B?dlN4NnVpU1lCSGJlWW82UktsKzF2TG5uV1B2RXVtWTB5SW1YU2hiMWF6aUN3?= =?utf-8?B?NkZCM25raG9UWnlQSVBZUkpwQy9nNzdqSDdGM08wbUJlKzRjMkFnL2pPQXBh?= =?utf-8?B?d2NQQTVvd3JTM0lQSDg4eDNjdXhjdzRmcWVMZnpiV0VOaVd6a0RMY2w2ZVEv?= =?utf-8?B?am9jNVJZNDVhZ0JVaGh1VDhXcmdQcmYwZzA3RjZuZWpHSGZjMk5Yb0w1c0JO?= =?utf-8?B?S3RTeGR0VmZqWGxDTk51UWJHcm1WVU5IREx4dDRDWEs3eEJDR2RRS2RWcVhi?= =?utf-8?B?TW14ZzR5dmF1ZDBMRHhPYmFRUHpSa1lJeTc4aDd4aWZvSTIxR3JwbVZSRkFN?= =?utf-8?B?ZVFRdnpHbjlZWnhUVkFSNlpnRmRHQURoeitYQ3IybUx4cEhwZkIrT3g0ZmlH?= =?utf-8?B?RjkzNURVQVJ4UG1LZHhmWk5qaWkwTm80U2NXK0pqRnVDQnppMUF1eW1rYXl6?= =?utf-8?B?Z0l3dzRLcXdJY2QwaWNEaER1aW05aG41eGEyYkdPdU5GdEd1c3djcUdESmt1?= =?utf-8?B?MjNYZHVnUGNhVFhvN3RUZmhGczlENWZPektGNkZ5aHloQzhNZ2tleUFRU3ky?= =?utf-8?B?aUxDMEZCRlB4Y29zTUR1NUt6WDBYaVRvWkwvVDhUb2RqYnRHZHBOWXpqTmVk?= =?utf-8?B?eVE9PQ==?= X-MS-Exchange-CrossTenant-Network-Message-Id: 82f562ef-9f78-4668-f09f-08da917feec9 X-MS-Exchange-CrossTenant-AuthSource: MWHPR11MB1629.namprd11.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 08 Sep 2022 09:53:08.2088 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 46c98d88-e344-4ed4-8496-4ed7712e255d X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: xb6j8G1U2QHJOgUT4sYprXGc8iXFSd9wfl934r2c3kHzP5moY1ZgsdyRO3roNZgmMWmEQQlqlU9vwa++hmqCzhhV6Djjk3f15A8DpDh1utQ= X-MS-Exchange-Transport-CrossTenantHeadersStamped: MWHPR11MB0013 X-OriginatorOrg: intel.com X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org On Fri, Jul 29, 2022 at 10:55:45AM +0000, Juraj Linkeš wrote: > The library uses the pexpect python library and implements connection to > a node and two ways to interact with the node: > 1. Send a string with specified prompt which will be matched after > the string has been sent to the node. > 2. Send a command to be executed. No prompt is specified here. > > Signed-off-by: Owen Hilyard > Signed-off-by: Juraj Linkeš Comments inline below. Thanks, /Bruce > --- > dts/framework/exception.py | 57 ++++++++++ > dts/framework/ssh_pexpect.py | 205 +++++++++++++++++++++++++++++++++++ > dts/framework/utils.py | 12 ++ > 3 files changed, 274 insertions(+) > create mode 100644 dts/framework/exception.py > create mode 100644 dts/framework/ssh_pexpect.py > create mode 100644 dts/framework/utils.py > > diff --git a/dts/framework/exception.py b/dts/framework/exception.py > new file mode 100644 > index 0000000000..35e81a4d99 > --- /dev/null > +++ b/dts/framework/exception.py > @@ -0,0 +1,57 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2010-2014 Intel Corporation > +# Copyright(c) 2022 PANTHEON.tech s.r.o. > +# Copyright(c) 2022 University of New Hampshire > +# > + > +""" > +User-defined exceptions used across the framework. > +""" > + > + > +class TimeoutException(Exception): > + """ > + Command execution timeout. > + """ > + > + command: str > + output: str > + > + def __init__(self, command: str, output: str): > + self.command = command > + self.output = output > + > + def __str__(self) -> str: > + return f"TIMEOUT on {self.command}" > + > + def get_output(self) -> str: > + return self.output > + > + > +class SSHConnectionException(Exception): > + """ > + SSH connection error. > + """ > + > + host: str > + > + def __init__(self, host: str): > + self.host = host > + > + def __str__(self) -> str: > + return f"Error trying to connect with {self.host}" > + > + > +class SSHSessionDeadException(Exception): > + """ > + SSH session is not alive. > + It can no longer be used. > + """ > + > + host: str > + > + def __init__(self, host: str): > + self.host = host > + > + def __str__(self) -> str: > + return f"SSH session with {self.host} has died" > diff --git a/dts/framework/ssh_pexpect.py b/dts/framework/ssh_pexpect.py > new file mode 100644 > index 0000000000..e8f64515c0 > --- /dev/null > +++ b/dts/framework/ssh_pexpect.py > @@ -0,0 +1,205 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2010-2014 Intel Corporation > +# Copyright(c) 2022 PANTHEON.tech s.r.o. > +# Copyright(c) 2022 University of New Hampshire > +# > + > +import time > +from typing import Optional > + > +from pexpect import pxssh > + > +from .exception import SSHConnectionException, SSHSessionDeadException, TimeoutException > +from .logger import DTSLOG > +from .utils import GREEN, RED > + > +""" > +The module handles ssh sessions to TG and SUT. > +It implements the send_expect function to send commands and get output data. > +""" > + > + > +class SSHPexpect: > + username: str > + password: str > + node: str > + logger: DTSLOG > + magic_prompt: str > + > + def __init__( > + self, > + node: str, > + username: str, > + password: Optional[str], > + logger: DTSLOG, > + ): > + self.magic_prompt = "MAGIC PROMPT" > + self.logger = logger > + > + self.node = node > + self.username = username > + self.password = password or "" > + self.logger.info(f"ssh {self.username}@{self.node}") > + > + self._connect_host() > + > + def _connect_host(self) -> None: > + """ > + Create connection to assigned node. > + """ > + retry_times = 10 > + try: > + if ":" in self.node: Should this check and the relevant splitting below to assign to self.ip and self.port, not be don at init when the node is passed in? Certainly the splitting should probably be done outside the loop, rather than re-doing the split into ip and port 10 times? > + while retry_times: > + self.ip = self.node.split(":")[0] > + self.port = int(self.node.split(":")[1]) > + self.session = pxssh.pxssh(encoding="utf-8") > + try: > + self.session.login( > + self.ip, > + self.username, > + self.password, > + original_prompt="[$#>]", > + port=self.port, > + login_timeout=20, > + password_regex=r"(?i)(?:password:)|(?:passphrase for key)|(?i)(password for .+:)", > + ) > + except Exception as e: > + print(e) > + time.sleep(2) > + retry_times -= 1 > + print("retry %d times connecting..." % (10 - retry_times)) > + else: > + break > + else: > + raise Exception("connect to %s:%s failed" % (self.ip, self.port)) > + else: > + self.session = pxssh.pxssh(encoding="utf-8") > + self.session.login( > + self.node, > + self.username, > + self.password, > + original_prompt="[$#>]", > + password_regex=r"(?i)(?:password:)|(?:passphrase for key)|(?i)(password for .+:)", > + ) > + self.logger.info(f"Connection to {self.node} succeeded") > + self.send_expect("stty -echo", "#") > + self.send_expect("stty columns 1000", "#") > + except Exception as e: > + print(RED(str(e))) > + if getattr(self, "port", None): > + suggestion = ( > + "\nSuggession: Check if the firewall on [ %s ] " % self.ip > + + "is stopped\n" > + ) I'd suggest using f-strings here to avoid splitting error messages across lines. They can also be used for strings above too to increase readability. We should probably look to standardize all strings used in DTS to a single format - either f-strings or the style given here, rather than using a mix. > + print(GREEN(suggestion)) > + > + raise SSHConnectionException(self.node) > + > + def send_expect_base(self, command: str, expected: str, timeout: float) -> str: > + self.clean_session() > + self.session.PROMPT = expected > + self.__sendline(command) > + self.__prompt(command, timeout) > + > + before = self.get_output_before() > + return before > + > + def send_expect( > + self, command: str, expected: str, timeout: float = 15, verify: bool = False > + ) -> str | int: > + > + try: > + ret = self.send_expect_base(command, expected, timeout) > + if verify: > + ret_status = self.send_expect_base("echo $?", expected, timeout) > + if not int(ret_status): > + return ret > + else: > + self.logger.error("Command: %s failure!" % command) > + self.logger.error(ret) > + return int(ret_status) > + else: > + return ret > + except Exception as e: > + print( > + RED( > + "Exception happened in [%s] and output is [%s]" > + % (command, self.get_output_before()) > + ) > + ) > + raise e > + > + def send_command(self, command: str, timeout: float = 1) -> str: > + try: > + self.clean_session() > + self.__sendline(command) > + except Exception as e: > + raise e > + > + output = self.get_session_before(timeout=timeout) > + self.session.PROMPT = self.session.UNIQUE_PROMPT > + self.session.prompt(0.1) > + > + return output > + > + def clean_session(self) -> None: > + self.get_session_before(timeout=0.01) > + > + def get_session_before(self, timeout: float = 15) -> str: > + """ > + Get all output before timeout > + """ > + self.session.PROMPT = self.magic_prompt > + try: > + self.session.prompt(timeout) > + except Exception as e: > + pass > + > + before = self.get_output_all() > + self.__flush() > + > + return before > + > + def __flush(self) -> None: > + """ > + Clear all session buffer > + """ > + self.session.buffer = "" > + self.session.before = "" > + > + def __prompt(self, command: str, timeout: float) -> None: > + if not self.session.prompt(timeout): > + raise TimeoutException(command, self.get_output_all()) from None > + > + def __sendline(self, command: str) -> None: > + if not self.isalive(): > + raise SSHSessionDeadException(self.node) > + if len(command) == 2 and command.startswith("^"): > + self.session.sendcontrol(command[1]) > + else: > + self.session.sendline(command) > + > + def get_output_before(self) -> str: > + if not self.isalive(): > + raise SSHSessionDeadException(self.node) > + before: list[str] = self.session.before.rsplit("\r\n", 1) The cast in the middle of pyton code seems strange. Does rsplit not always return a list value? In quick testing here even doing "".rsplit("x",1) returns a list with the empty string. > + if before[0] == "[PEXPECT]": > + before[0] = "" > + > + return before[0] Might be slightly more readable as: retval = self.session.before.rsplit("\r\n", 1)[0] if retval == "[PEXPECT]" return "" return retval > + > + def get_output_all(self) -> str: > + output: str = self.session.before > + output.replace("[PEXPECT]", "") > + return output > + This function is missing the isalive() check in the previous function above. Also, could you rewrite "get_output_before" to use "get_output_all" to avoid having checks for [PEXPECT] in multiple places - and also checks for isalive(). > + def close(self, force: bool = False) -> None: > + if force is True: > + self.session.close() > + else: > + if self.isalive(): > + self.session.logout() > + > + def isalive(self) -> bool: > + return self.session.isalive() > diff --git a/dts/framework/utils.py b/dts/framework/utils.py > new file mode 100644 > index 0000000000..db87349827 > --- /dev/null > +++ b/dts/framework/utils.py > @@ -0,0 +1,12 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2010-2014 Intel Corporation > +# Copyright(c) 2022 PANTHEON.tech s.r.o. > +# Copyright(c) 2022 University of New Hampshire > +# > + > +def RED(text: str) -> str: > + return f"\u001B[31;1m{str(text)}\u001B[0m" > + > + > +def GREEN(text: str) -> str: > + return f"\u001B[32;1m{str(text)}\u001B[0m" > -- > 2.30.2 >