diff --git a/.gitignore b/.gitignore index 7b85cc34175..a00b0a4f139 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,9 @@ tags .vscode/ features/FEATURE_BLE/targets/TARGET_CORDIO/stack_backup/ + +.pytest_cache +log + +# Icetea related file +test_suite.json diff --git a/TEST_APPS/__init__.py b/TEST_APPS/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/TEST_APPS/device/exampleapp/main.cpp b/TEST_APPS/device/exampleapp/main.cpp new file mode 100644 index 00000000000..8e1654e07f7 --- /dev/null +++ b/TEST_APPS/device/exampleapp/main.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#include +#include +#include "mbed.h" +#include "mbed-client-cli/ns_cmdline.h" + +#ifndef ICETEA_EXAMPLE_ENABLED +#error [NOT_SUPPORTED] Skipping example application. +#endif +/** + * Macros for setting console flow control. + */ +#define CONSOLE_FLOWCONTROL_RTS 1 +#define CONSOLE_FLOWCONTROL_CTS 2 +#define CONSOLE_FLOWCONTROL_RTSCTS 3 +#define mbed_console_concat_(x) CONSOLE_FLOWCONTROL_##x +#define mbed_console_concat(x) mbed_console_concat_(x) +#define CONSOLE_FLOWCONTROL mbed_console_concat(MBED_CONF_TARGET_CONSOLE_UART_FLOW_CONTROL) + +#define SERIAL_CONSOLE_BAUD_RATE 115200 + +void cmd_ready_cb(int retcode) +{ + cmd_next(retcode); +} + +void wrap_printf(const char *f, va_list a) +{ + vprintf(f, a); +} + +int main() +{ + cmd_init(&wrap_printf); + + int c; + while ((c = getchar()) != EOF) { + cmd_char_input(c); + } + return 0; +} + +FileHandle *mbed::mbed_override_console(int) +{ + static UARTSerial console(STDIO_UART_TX, STDIO_UART_RX, SERIAL_CONSOLE_BAUD_RATE); +#if CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTS + console.set_flow_control(SerialBase::RTS, STDIO_UART_RTS, NC); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_CTS + console.set_flow_control(SerialBase::CTS, NC, STDIO_UART_CTS); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTSCTS + console.set_flow_control(SerialBase::RTSCTS, STDIO_UART_RTS, STDIO_UART_CTS); +#endif + return &console; +} diff --git a/TEST_APPS/device/socket_app/cmd_ifconfig.cpp b/TEST_APPS/device/socket_app/cmd_ifconfig.cpp new file mode 100644 index 00000000000..cb55e3bedff --- /dev/null +++ b/TEST_APPS/device/socket_app/cmd_ifconfig.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#include "NetworkStack.h" +#include "NetworkInterface.h" + +#include "mbed-client-cli/ns_cmdline.h" +#include "mbed-trace/mbed_trace.h" + +#include "ip4string.h" + +#define WIFI 2 +#if !defined(MBED_CONF_TARGET_NETWORK_DEFAULT_INTERFACE_TYPE) || \ + (MBED_CONF_TARGET_NETWORK_DEFAULT_INTERFACE_TYPE == WIFI && !defined(MBED_CONF_NSAPI_DEFAULT_WIFI_SSID)) +#error [NOT_SUPPORTED] No network configuration found for this target. +#endif + +#include + +#define TRACE_GROUP "Aifc" + +NetworkInterface *net; + +NetworkInterface *get_interface(void) +{ + return net; +} + +int cmd_ifup(int argc, char *argv[]); +int cmd_ifdown(int argc, char *argv[]); +int cmd_ifconfig(int argc, char *argv[]); + +const char *MAN_IFCONFIG = " ifup interface up\r\n"\ + " ifdown interface down\r\n"; + +static void ifconfig_print() +{ + if (!net) { + cmd_printf("No interface configured\r\n"); + return; + } + const char *str = net->get_ip_address(); + if (str) { + uint8_t buf[4]; + if (stoip4(str, strlen(str), buf)) { + cmd_printf("IPv4 if addr: %s\r\n", str); + } else { + cmd_printf("IPv6 if addr:\r\n [0]: %s\r\n", str); + } + } else { + cmd_printf("No IP address\r\n"); + } + str = net->get_mac_address(); + if (str) { + cmd_printf("MAC-48: %s\r\n", str); + } else { + cmd_printf("MAC-48: unknown\r\n"); + } +} + + +void cmd_ifconfig_init(void) +{ + cmd_add("ifup", cmd_ifup, "ifconfig up", MAN_IFCONFIG); + cmd_add("ifdown", cmd_ifdown, "ifconfig down", MAN_IFCONFIG); + cmd_add("ifconfig", cmd_ifconfig, "ifconfig", MAN_IFCONFIG); +} + +int cmd_ifconfig(int argc, char *argv[]) +{ + ifconfig_print(); + return CMDLINE_RETCODE_SUCCESS; +} + +int cmd_ifup(int argc, char *argv[]) +{ + if (!net) { + net = NetworkInterface::get_default_instance(); + } + int err = net->connect(); + if (err != NSAPI_ERROR_OK) { + return CMDLINE_RETCODE_FAIL; + } + + ifconfig_print(); + return CMDLINE_RETCODE_SUCCESS; +} + +int cmd_ifdown(int argc, char *argv[]) +{ + if (!net) { + return CMDLINE_RETCODE_FAIL; + } + int err = net->disconnect(); + if (err != NSAPI_ERROR_OK) { + return CMDLINE_RETCODE_FAIL; + } + + return CMDLINE_RETCODE_SUCCESS; +} + + + +const char *networkstack_error_to_str(int errorcode) +{ + switch (errorcode) { + case NSAPI_ERROR_OK: + return "NSAPI_ERROR_OK"; + case NSAPI_ERROR_WOULD_BLOCK: + return "NSAPI_ERROR_WOULD_BLOCK"; + case NSAPI_ERROR_UNSUPPORTED: + return "NSAPI_ERROR_UNSUPPORTED"; + case NSAPI_ERROR_PARAMETER: + return "NSAPI_ERROR_PARAMETER"; + case NSAPI_ERROR_NO_CONNECTION: + return "NSAPI_ERROR_NO_CONNECTION"; + case NSAPI_ERROR_NO_SOCKET: + return "NSAPI_ERROR_NO_SOCKET"; + case NSAPI_ERROR_NO_ADDRESS: + return "NSAPI_ERROR_NO_ADDRESS"; + case NSAPI_ERROR_NO_MEMORY: + return "NSAPI_ERROR_NO_MEMORY"; + case NSAPI_ERROR_NO_SSID: + return "NSAPI_ERROR_NO_SSID"; + case NSAPI_ERROR_DNS_FAILURE: + return "NSAPI_ERROR_DNS_FAILURE"; + case NSAPI_ERROR_DHCP_FAILURE: + return "NSAPI_ERROR_DHCP_FAILURE"; + case NSAPI_ERROR_AUTH_FAILURE: + return "NSAPI_ERROR_AUTH_FAILURE"; + case NSAPI_ERROR_DEVICE_ERROR: + return "NSAPI_ERROR_DEVICE_ERROR"; + case NSAPI_ERROR_IN_PROGRESS: + return "NSAPI_ERROR_IN_PROGRESS"; + case NSAPI_ERROR_ALREADY: + return "NSAPI_ERROR_ALREADY"; + case NSAPI_ERROR_IS_CONNECTED: + return "NSAPI_ERROR_IS_CONNECTED"; + case NSAPI_ERROR_CONNECTION_LOST: + return "NSAPI_ERROR_CONNECTION_LOST"; + case NSAPI_ERROR_CONNECTION_TIMEOUT: + return "NSAPI_ERROR_CONNECTION_TIMEOUT"; + default: + return "unknown error code"; + } +} diff --git a/TEST_APPS/device/socket_app/cmd_ifconfig.h b/TEST_APPS/device/socket_app/cmd_ifconfig.h new file mode 100644 index 00000000000..37d50b942b7 --- /dev/null +++ b/TEST_APPS/device/socket_app/cmd_ifconfig.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#ifndef CMD_IFCONFIG_H +#define CMD_IFCONFIG_H + +#include "NetworkInterface.h" +#include "NetworkStack.h" + +/** Get a pointer to a network interface instance + * + * Allowed interface types (depend on application configurations): + * cell0, wlan0, eth0, mesh0 + * + * @return pointer to the network interface, or NULL if unrecognized or ambiguous + */ +NetworkInterface *get_interface(void); + +void cmd_ifconfig_init(void); +const char *networkstack_error_to_str(int errorcode); + +#endif diff --git a/TEST_APPS/device/socket_app/cmd_socket.cpp b/TEST_APPS/device/socket_app/cmd_socket.cpp new file mode 100644 index 00000000000..4aefe700dc7 --- /dev/null +++ b/TEST_APPS/device/socket_app/cmd_socket.cpp @@ -0,0 +1,1207 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#include "mbed.h" +#include "NetworkStack.h" +#include "UDPSocket.h" +#include "TCPSocket.h" +#include "TCPServer.h" +#include "NetworkInterface.h" +#include "SocketAddress.h" +#include "Queue.h" + +#include +#include +#include +#include +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include + +#include "mbed-client-cli/ns_cmdline.h" +#include "mbed-trace/mbed_trace.h" +#include "strconv.h" + +#define TRACE_GROUP "Asck" + +#include "cmd_ifconfig.h" +#include "cmd_socket.h" + +#define SIGNAL_SIGIO 0x1 +#define PACKET_SIZE_ARRAY_LEN 5 + +#define MAN_SOCKET "\r\nSOCKET API\r\n"\ + "\r\n"\ + "socket [options]\r\n\r\n"\ + " new \r\n" \ + " type: UDPSocket|TCPSocket|TCPServer\r\n"\ + " return socket id\r\n"\ + " delete\r\n"\ + " remote the space allocated for Socket\r\n"\ + " open\r\n"\ + " close\r\n"\ + " bind [port] [addr ]\r\n"\ + " set_blocking \r\n"\ + " set_timeout \r\n"\ + " register_sigio_cb\r\n"\ + " set_RFC_864_pattern_check \r\n"\ + "\r\nFor UDPSocket\r\n"\ + " sendto (\"msg\" | --data_len )\r\n"\ + " \"msg\" Send packet with defined string content\r\n"\ + " --data_len Send packet with random content with size \r\n"\ + " recvfrom \r\n"\ + " start_udp_receiver_thread --max_data_len [--packets ]\r\n"\ + " --max_data_len Size of input buffer to fill up\r\n"\ + " --packets Receive N number of packets, default 1\r\n"\ + "\r\nFor TCPSocket\r\n"\ + " connect \r\n"\ + " send (\"msg\" | --data_len )\r\n"\ + " recv \r\n"\ + " start_tcp_receiver_thread --data_len [--max_recv_len ] [--repeat ]\r\n"\ + " --data_len Size of input buffer to fill up\r\n"\ + " --max_recv_len Read maximum of L bytes in at a time. Default full buffer \r\n"\ + " --repeat Repeat buffer filling N times, default 1\r\n"\ + " join_tcp_receiver_thread\r\n"\ + " start_bg_traffic_thread\r\n"\ + " join_bg_traffic_thread\r\n"\ + " setsockopt_keepalive \r\n"\ + " getsockopt_keepalive\r\n"\ + "\r\nFor TCPServer\r\n"\ + " listen [backlog]\r\n"\ + " accept \r\n"\ + " accept new connection into socket. Requires to be pre-allocated.\r\n"\ + "\r\nOther options\r\n"\ + " print-mode [--string|--hex|--disabled] [--col-width ]" + +class SInfo; +static Queue event_queue; +static int id_count = 0; + +class SInfo { +public: + enum SocketType { + TCP_CLIENT, + TCP_SERVER, + UDP + }; + SInfo(TCPSocket *sock): + _id(id_count++), + _sock(sock), + _type(SInfo::TCP_CLIENT), + _blocking(true), + _dataLen(0), + _maxRecvLen(0), + _repeatBufferFill(1), + _receivedTotal(0), + _receiverThread(NULL), + _receiveBuffer(NULL), + _senderThreadId(NULL), + _receiverThreadId(NULL), + _packetSizes(NULL), + _check_pattern(false) + { + assert(sock); + } + SInfo(TCPServer *sock): + _id(id_count++), + _sock(sock), + _type(SInfo::TCP_SERVER), + _blocking(true), + _dataLen(0), + _maxRecvLen(0), + _repeatBufferFill(1), + _receivedTotal(0), + _receiverThread(NULL), + _receiveBuffer(NULL), + _senderThreadId(NULL), + _receiverThreadId(NULL), + _packetSizes(NULL), + _check_pattern(false) + { + assert(sock); + } + SInfo(UDPSocket *sock): + _id(id_count++), + _sock(sock), + _type(SInfo::UDP), + _blocking(true), + _dataLen(0), + _maxRecvLen(0), + _repeatBufferFill(1), + _receivedTotal(0), + _receiverThread(NULL), + _receiveBuffer(NULL), + _senderThreadId(NULL), + _receiverThreadId(NULL), + _packetSizes(NULL), + _check_pattern(false) + { + assert(sock); + } + ~SInfo() + { + this->_sock->sigio(Callback()); + if (this->_receiverThread) { + this->_receiverThread->terminate(); + delete this->_receiverThread; + } + if (this->_receiveBuffer) { + delete this->_receiveBuffer; + } + delete this->_sock; + } + int id() const + { + return this->_id; + } + Socket &socket() + { + return *(this->_sock); + } + Socket &socket() const + { + return *(this->_sock); + } + TCPSocket *tcp_socket() + { + return this->_type == SInfo::TCP_CLIENT ? static_cast(this->_sock) : NULL; + } + TCPServer *tcp_server() + { + return this->_type == SInfo::TCP_SERVER ? static_cast(this->_sock) : NULL; + } + UDPSocket *udp_socket() + { + return this->_type == SInfo::UDP ? static_cast(this->_sock) : NULL; + } + SInfo::SocketType type() const + { + return this->_type; + } + void setDataCount(int dataCount) + { + this->_dataLen = dataCount; + } + int getDataCount() + { + return this->_dataLen; + } + void setReceiverThread(Thread *receiverThread) + { + this->_receiverThread = receiverThread; + } + Thread *getReceiverThread() + { + return this->_receiverThread; + } + void setReceiveBuffer(uint8_t *receiveBuffer) + { + this->_receiveBuffer = receiveBuffer; + } + uint8_t *getReceiveBuffer() + { + return this->_receiveBuffer; + } + void setMaxRecvLen(int recvLen) + { + this->_maxRecvLen = recvLen; + } + int getMaxRecvLen() + { + return this->_maxRecvLen; + } + void setRepeatBufferFill(int n) + { + this->_repeatBufferFill = n; + } + int getRepeatBufferFill() + { + return this->_repeatBufferFill; + } + void setRecvTotal(int n) + { + this->_receivedTotal = n; + } + int getRecvTotal() + { + return this->_receivedTotal; + } + void setSenderThreadId(osThreadId threadID) + { + this->_senderThreadId = threadID; + } + void setReceiverThreadId(osThreadId threadID) + { + this->_receiverThreadId = threadID; + } + osThreadId getSenderThreadId() + { + return this->_senderThreadId; + } + osThreadId getReceiverThreadId() + { + return this->_receiverThreadId; + } + void setPacketSizeArray(int *ptr) + { + this->_packetSizes = ptr; + } + int *getPacketSizeArray() + { + return this->_packetSizes; + } + void setUnavailable() + { + this->_available = false; + } + void setAvailable() + { + this->_available = true; + } + bool available() + { + return this->_available; + } + void set_pattern_check(bool enabled) + { + _check_pattern = enabled; + }; + bool check_pattern(void *buffer, size_t len); + + const char *type_str() const + { + const char *str; + switch (this->_type) { + case SInfo::TCP_CLIENT: + str = "TCPSocket"; + break; + case SInfo::TCP_SERVER: + str = "TCPServer"; + break; + case SInfo::UDP: + str = "UDPSocket"; + break; + default: + assert(0); + break; + } + return str; + } + bool blocking() const + { + return this->_blocking; + } + void set_blocking(bool blocking) + { + socket().set_blocking(blocking); + this->_blocking = blocking; + } + bool can_connect() + { + return (this->type() == SInfo::TCP_CLIENT); + } + bool can_bind() + { + return (this->type() == SInfo::UDP || this->type() == SInfo::TCP_SERVER); + } + bool can_send() + { + return (this->type() == SInfo::TCP_CLIENT); + } + bool can_recv() + { + return (this->type() == SInfo::TCP_CLIENT); + } + bool can_sendto() + { + return (this->type() == SInfo::UDP); + } + bool can_recvfrom() + { + return (this->type() == SInfo::UDP); + } + bool can_listen() + { + return (this->type() == SInfo::TCP_SERVER); + } + bool can_accept() + { + return (this->type() == SInfo::TCP_SERVER); + } +private: + const int _id; + Socket *_sock; + const SInfo::SocketType _type; + bool _blocking; + int _dataLen; + int _maxRecvLen; + int _repeatBufferFill; + int _receivedTotal; + Thread *_receiverThread; + uint8_t *_receiveBuffer; + osThreadId _senderThreadId; + osThreadId _receiverThreadId; + int *_packetSizes; + bool _available; + bool _check_pattern; + + SInfo(); +}; + +static std::vector m_sockets; + +static enum { + PRINT_DISABLED, + PRINT_STRING, + PRINT_HEX +} printing_mode = PRINT_STRING; +static int printing_col_width = 20; + +static int cmd_socket(int argc, char *argv[]); +static void print_data(const uint8_t *buf, int len); +static void print_data_as_string(const uint8_t *buf, int len, int col_width); +static void print_data_as_hex(const uint8_t *buf, int len, int col_width); + +/** Generate RFC 864 example pattern. + * + * Pattern is 72 chraracter lines of the ASCII printing characters ending with "\r\n". + * There are 95 printing characters in the ASCII character set. + * Example: `nc echo.mbedcloudtesting.com 19 | dd bs=1 count=222` + * !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg + * !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh + * "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi + * + * NOTE: Pattern starts with space, not ! + * + * \param offset Start pattern from offset + * \param len Length of pattern to generate. + */ +static void generate_RFC_864_pattern(size_t offset, uint8_t *buf, size_t len) +{ + while (len--) { + if (offset % 74 == 72) { + *buf++ = '\r'; + } else if (offset % 74 == 73) { + *buf++ = '\n'; + } else { + *buf++ = ' ' + (offset % 74 + offset / 74) % 95 ; + } + offset++; + } +} + +bool SInfo::check_pattern(void *buffer, size_t len) +{ + if (!_check_pattern) { + return true; + } + void *buf = malloc(len); + if (!buf) { + return false; + } + size_t offset = _receivedTotal; + generate_RFC_864_pattern(offset, (uint8_t *)buf, len); + bool match = memcmp(buf, buffer, len) == 0; + if (!match) { + cmd_printf("Pattern check failed\r\nWAS:%.*s\r\nREF:%.*s\r\n", len, (char *)buffer, len, (char *)buf); + } + free(buf); + return match; +} + +static void sigio_handler(SInfo *info) +{ + if (info->getReceiverThreadId()) { + osSignalSet(info->getReceiverThreadId(), SIGNAL_SIGIO); + } + if (info->getSenderThreadId()) { + osSignalSet(info->getSenderThreadId(), SIGNAL_SIGIO); + } +} + +void cmd_socket_init(void) +{ + cmd_add("socket", cmd_socket, "socket", MAN_SOCKET); +} + +int handle_nsapi_error(const char *function, nsapi_error_t ret) +{ + if (ret != NSAPI_ERROR_OK) { + cmd_printf("%s returned: %d -> %s\r\n", function, ret, networkstack_error_to_str(ret)); + return CMDLINE_RETCODE_FAIL; + } + return CMDLINE_RETCODE_SUCCESS; +} + +int handle_nsapi_size_or_error(const char *function, nsapi_size_or_error_t ret) +{ + if (ret < 0) { + return handle_nsapi_error(function, ret); + } else { + cmd_printf("%s returned: %d\r\n", function, ret); + } + return CMDLINE_RETCODE_SUCCESS; +} + +static SInfo *get_sinfo(int id) +{ + + for (std::vector::iterator it = m_sockets.begin(); it != m_sockets.end(); it++) { + if ((*it)->id() == id) { + return *it; + } + } + return NULL; +} + +static int del_sinfo(SInfo *info) +{ + for (std::vector::iterator it = m_sockets.begin(); it != m_sockets.end(); it++) { + if ((*it) == info) { + delete info; + m_sockets.erase(it); + return CMDLINE_RETCODE_SUCCESS; + } + } + return CMDLINE_RETCODE_FAIL; +} + +static int cmd_socket_new(int argc, char *argv[]) +{ + const char *s; + SInfo *info; + + if (cmd_parameter_last(argc, argv)) { + s = cmd_parameter_last(argc, argv); + if (strcmp(s, "UDPSocket") == 0) { + tr_debug("Creating a new UDPSocket"); + info = new SInfo(new UDPSocket); + } else if (strcmp(s, "TCPSocket") == 0) { + tr_debug("Creating a new TCPSocket"); + info = new SInfo(new TCPSocket); + } else if (strcmp(s, "TCPServer") == 0) { + tr_debug("Creating a new TCPServer"); + info = new SInfo(new TCPServer); + } else { + cmd_printf("unsupported protocol: %s\r\n", s); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + } else { + cmd_printf("Must specify socket type\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + // Note, this cannot fail. We either succeed or "new" woud call exit(1) in failure. + // We did not ask no_throw version of it. + cmd_printf("new socket. sid: %d\r\n", info->id()); + m_sockets.push_back(info); + return CMDLINE_RETCODE_SUCCESS; +} + +static void udp_receiver_thread(SInfo *info) +{ + SocketAddress addr; + int i = 0, received = 0; + int n = info->getRepeatBufferFill(); + int *packetSizes = info->getPacketSizeArray(); + nsapi_size_or_error_t ret = 0; + + info->setReceiverThreadId(Thread::gettid()); + + while (i < n) { + ret = static_cast(info->socket()).recvfrom(&addr, info->getReceiveBuffer() + received, info->getDataCount() - received); + if (ret > 0) { + if (!info->check_pattern(info->getReceiveBuffer() + received, ret)) { + return; + } + received += ret; + packetSizes[i % PACKET_SIZE_ARRAY_LEN] = ret; + i++; + info->setRecvTotal(info->getRecvTotal() + ret); + } else if (ret == NSAPI_ERROR_WOULD_BLOCK) { + Thread::signal_wait(SIGNAL_SIGIO); + } else { + handle_nsapi_size_or_error("Thread: UDPSocket::recvfrom()", ret); + return; + } + } +} + +static nsapi_size_or_error_t start_udp_receiver_thread(SInfo *info, int argc, char *argv[]) +{ + int32_t max_size; + int32_t n = 1; + + if (!cmd_parameter_int(argc, argv, "--max_data_len", &max_size)) { + cmd_printf("Need data max data size\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + if (cmd_parameter_index(argc, argv, "--packets") > 0) { + if (!cmd_parameter_int(argc, argv, "--packets", &n)) { + cmd_printf("Need number of packets\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + } + uint8_t *dataIn = (uint8_t *)malloc(max_size + 1); + if (!dataIn) { + cmd_printf("malloc() failed\r\n"); + return CMDLINE_RETCODE_FAIL; + } + int *packetSizes = new (nothrow) int[PACKET_SIZE_ARRAY_LEN]; + if (!packetSizes) { + cmd_printf("Allocation failed\r\n"); + return CMDLINE_RETCODE_FAIL; + } + for (int i = 0; i < PACKET_SIZE_ARRAY_LEN; i++) { + packetSizes[i] = 0; + } + memset(dataIn, 0x00, max_size + 1); + info->setReceiveBuffer(dataIn); + info->setDataCount(max_size); + info->setRepeatBufferFill(n); + info->setPacketSizeArray(packetSizes); + info->setReceiverThread(new Thread()); + info->getReceiverThread()->start(callback(udp_receiver_thread, info)); + return CMDLINE_RETCODE_SUCCESS; +} + +static nsapi_size_or_error_t udp_sendto_command_handler(SInfo *info, int argc, char *argv[]) +{ + char *host; + int32_t port; + + if (!cmd_parameter_val(argc, argv, "sendto", &host)) { + cmd_printf("Need host name\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + if (!cmd_parameter_int(argc, argv, host, &port)) { + cmd_printf("Need port number\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + // Replace NULL-strings with NULL + host = strcmp(host, "NULL") ? host : NULL; + + int32_t len; + void *data; + if (cmd_parameter_int(argc, argv, "--data_len", &len)) { + data = malloc(len); + if (!data) { + cmd_printf("Failed to allocate memory\r\n"); + return CMDLINE_RETCODE_FAIL; + } + } else { + // Replace NULL-strings with NULL + if (strcmp(argv[5], "NULL") == 0) { + data = NULL; + len = 0; + } else { + data = argv[5]; + len = strlen(argv[5]); + } + } + + nsapi_size_or_error_t ret = static_cast(info->socket()).sendto(host, port, data, len); + if (ret > 0) { + cmd_printf("sent: %d bytes\r\n", ret); + } + if (data != argv[5]) { + free(data); + } + + return handle_nsapi_size_or_error("UDPSocket::sendto()", ret); +} + +static nsapi_size_or_error_t udp_recvfrom_command_handler(SInfo *info, int argc, char *argv[]) +{ + SocketAddress addr; + int32_t len; + + if (!cmd_parameter_int(argc, argv, "recvfrom", &len)) { + cmd_printf("Need len\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + + void *data = malloc(len); + if (!data) { + cmd_printf("malloc() failed\r\n"); + return CMDLINE_RETCODE_FAIL; + } + nsapi_size_or_error_t ret = static_cast(info->socket()).recvfrom(&addr, data, len); + if (ret > 0) { + cmd_printf("UDPSocket::recvfrom, addr=%s port=%d\r\n", addr.get_ip_address(), addr.get_port()); + cmd_printf("received: %d bytes\r\n", ret); + print_data((const uint8_t *)data, len); + if (!info->check_pattern(data, len)) { + ret = -1; + } + info->setRecvTotal(info->getRecvTotal() + ret); + } + free(data); + return handle_nsapi_size_or_error("UDPSocket::recvfrom()", ret); +} + +static void tcp_receiver_thread(SInfo *info) +{ + int i, received; + int n = info->getRepeatBufferFill(); + int recv_len = info->getMaxRecvLen(); + int bufferSize = info->getDataCount(); + nsapi_size_or_error_t ret = 0; + + info->setReceiverThreadId(Thread::gettid()); + + for (i = 0; i < n; i++) { + received = 0; + while (received < bufferSize) { + ret = static_cast(info->socket()).recv(info->getReceiveBuffer() + received, recv_len - received); + if (ret > 0) { + if (!info->check_pattern(info->getReceiveBuffer() + received, ret)) { + return; + } + received += ret; + info->setRecvTotal(info->getRecvTotal() + ret); + } else if (ret == NSAPI_ERROR_WOULD_BLOCK) { + Thread::signal_wait(SIGNAL_SIGIO); + } else { + handle_nsapi_size_or_error("Thread: TCPSocket::recv()", ret); + return; + } + } + } +} + +static nsapi_size_or_error_t start_tcp_receiver_thread(SInfo *info, int argc, char *argv[]) +{ + int32_t len; + int32_t recv_len; + int32_t n = 1; + + if (!cmd_parameter_int(argc, argv, "--data_len", &len)) { + cmd_printf("Need data len\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + recv_len = len; + + if (cmd_parameter_index(argc, argv, "--max_recv_len") > 0) { + if (!cmd_parameter_int(argc, argv, "--max_recv_len", &recv_len)) { + cmd_printf("Need max recv len\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + } + + if (cmd_parameter_index(argc, argv, "--repeat") > 0) { + if (!cmd_parameter_int(argc, argv, "--repeat", &n)) { + cmd_printf("Need repeat number\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + } + + uint8_t *dataIn = (uint8_t *)malloc(len + 1); + if (!dataIn) { + cmd_printf("malloc() failed\r\n"); + return CMDLINE_RETCODE_FAIL; + } + + info->setReceiveBuffer(dataIn); + info->setDataCount(len); + info->setMaxRecvLen(recv_len); + info->setRepeatBufferFill(n); + info->setReceiverThread(new Thread()); + info->getReceiverThread()->start(callback(tcp_receiver_thread, info)); + return CMDLINE_RETCODE_SUCCESS; +} + +static nsapi_size_or_error_t tcp_send_command_handler(SInfo *info, int argc, char *argv[]) +{ + int32_t len; + void *data; + nsapi_size_or_error_t ret = 0; + + info->setSenderThreadId(Thread::gettid()); + + if (cmd_parameter_int(argc, argv, "--data_len", &len)) { + data = malloc(len); + if (!data) { + cmd_printf("Failed to allocate memory\r\n"); + return CMDLINE_RETCODE_FAIL; + } + } else { + data = argv[3]; + len = strlen(argv[3]); + } + + ret = static_cast(info->socket()).send(data, len); + + if (ret > 0) { + cmd_printf("sent: %d bytes\r\n", ret); + } + if (data != argv[3]) { + free(data); + } + return handle_nsapi_size_or_error("TCPSocket::send()", ret); +} + +static nsapi_size_or_error_t tcp_recv_command_handler(SInfo *info, int argc, char *argv[]) +{ + int32_t len; + + if (!cmd_parameter_int(argc, argv, "recv", &len)) { + cmd_printf("Need length\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + + void *data = malloc(len); + if (!data) { + cmd_printf("malloc() failed\r\n"); + return CMDLINE_RETCODE_FAIL; + } + + nsapi_size_or_error_t ret = static_cast(info->socket()).recv(data, len); + if (ret > 0) { + cmd_printf("received: %d bytes\r\n", ret); + print_data((const uint8_t *)data, ret); + if (!info->check_pattern(data, ret)) { + ret = -1; + } + info->setRecvTotal(info->getRecvTotal() + ret); + } + free(data); + return handle_nsapi_size_or_error("TCPSocket::recv()", ret); +} + +static nsapi_size_or_error_t recv_all(char *const rbuffer, const int expt_len, SInfo *const info) +{ + int rtotal, rbytes; + char *rhead; + + rtotal = 0; + rhead = rbuffer; + + while (rtotal < expt_len) { + rbytes = info->tcp_socket()->recv(rhead, expt_len); + if (rbytes <= 0) { // Connection closed abruptly + rbuffer[rtotal] = '\0'; + return rbytes; + } + rtotal += rbytes; + rhead = rbuffer + rtotal; + } + rbuffer[rtotal] = '\0'; + info->setRecvTotal(info->getRecvTotal() + rtotal); + return rtotal; +} + +static void bg_traffic_thread(SInfo *info) +{ + static const int data_len = 10; + char sbuffer[data_len + 1] = "dummydata_"; + char rbuffer[data_len + 1]; + int scount, rtotal = 0; + info->setSenderThreadId(Thread::gettid()); + + for (;;) { + if (!info->available()) { + (void)handle_nsapi_size_or_error(__func__, rtotal); + break; + } + sbuffer[data_len - 1] = 'A' + (rand() % 26); + scount = info->tcp_socket()->send(sbuffer, data_len); + rtotal = recv_all(rbuffer, data_len, info); + + if (scount != rtotal || (strcmp(sbuffer, rbuffer) != 0)) { + info->setUnavailable(); + + tr_err("Background received data does not match to sent data"); + tr_err("Background sent: \"%s\"", sbuffer); + tr_err("Background received: \"%s\"", rbuffer); + } + wait_ms(10); + } +} + +static nsapi_size_or_error_t start_bg_traffic_thread(SInfo *info, int argc, char *argv[]) +{ + info->setReceiverThread(new Thread()); + info->setAvailable(); + info->getReceiverThread()->start(callback(bg_traffic_thread, info)); + return CMDLINE_RETCODE_SUCCESS; +} + +static void thread_clean_up(SInfo *info) +{ + if (info->getReceiverThread()) { + delete info->getReceiverThread(); + info->setReceiverThread(NULL); + } + if (info->getReceiveBuffer()) { + delete info->getReceiveBuffer(); + info->setReceiveBuffer(NULL); + } + if (info->getPacketSizeArray()) { + delete[] info->getPacketSizeArray(); + info->setPacketSizeArray(NULL); + } +} + +static int cmd_socket(int argc, char *argv[]) +{ + if (cmd_parameter_index(argc, argv, "new") == 1) { + return cmd_socket_new(argc, argv); + } else if (cmd_parameter_index(argc, argv, "print-mode") > 0) { + if (cmd_parameter_index(argc, argv, "--string") > 0) { + printing_mode = PRINT_STRING; + } else if (cmd_parameter_index(argc, argv, "--hex") > 0) { + printing_mode = PRINT_HEX; + } else if (cmd_parameter_index(argc, argv, "--disabled") > 0) { + printing_mode = PRINT_DISABLED; + } + int32_t parsed_col_width = 0; + if (cmd_parameter_int(argc, argv, "--col-width", &parsed_col_width)) { + if (parsed_col_width <= 0) { + cmd_printf("Printing column width must be > 0"); + return CMDLINE_RETCODE_FAIL; + } + if (printing_mode == PRINT_HEX && parsed_col_width > 42) { + cmd_printf("Maximum column width for hex data is 42 bytes"); + return CMDLINE_RETCODE_FAIL; + } + printing_col_width = (int)parsed_col_width; + } + // Allow print-mode to be used as a parameter to other commands + if (cmd_parameter_index(argc, argv, "print-mode") == 1) { + return CMDLINE_RETCODE_SUCCESS; + } + } + + // Rest of the commands require Socket + SInfo *info = get_sinfo(strtol(argv[1], NULL, 10)); + if (!info) { + cmd_printf("Invalid socket id %s\r\n", argv[1]); + return CMDLINE_RETCODE_FAIL; + } + + bool enable_pattern_check; + if (cmd_parameter_bool(argc, argv, "set_RFC_864_pattern_check", &enable_pattern_check)) { + info->set_pattern_check(enable_pattern_check); + return CMDLINE_RETCODE_SUCCESS; + } + + // Helper macro for checking the which command was given +#define COMMAND_IS(cmd) (cmd_parameter_index(argc, argv, cmd) == 2) + + /* + * Generic Socket commands: + * delete, open, close, bind, set_blocking, set_timeout + */ + + if (COMMAND_IS("delete")) { + return del_sinfo(info); + + } else if (COMMAND_IS("open")) { + NetworkInterface *interface; + + interface = get_interface(); // get default interface + + if (!interface) { + cmd_printf("Invalid interface\r\n"); + return CMDLINE_RETCODE_FAIL; + } + + switch (info->type()) { + case SInfo::TCP_CLIENT: + return handle_nsapi_error("Socket::open()", info->tcp_socket()->open(interface)); + case SInfo::UDP: + return handle_nsapi_error("Socket::open()", info->udp_socket()->open(interface)); + case SInfo::TCP_SERVER: + return handle_nsapi_error("Socket::open()", info->tcp_server()->open(interface)); + } + + } else if (COMMAND_IS("close")) { + return handle_nsapi_error("Socket::close()", info->socket().close()); + + } else if (COMMAND_IS("bind")) { + int32_t port = 0; + char *addr; + + if (!cmd_parameter_int(argc, argv, "port", &port) && !cmd_parameter_int(argc, argv, "bind", &port) && port <= 0 && port > 65535) { + printf("Missing or invalid port number\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + + if (cmd_parameter_val(argc, argv, "addr", &addr)) { + // Replace NULL-strings with NULL + addr = strcmp(addr, "NULL") ? addr : NULL; + cmd_printf("Socket::bind(%s, %" PRId32 ")\r\n", addr, port); + SocketAddress tmp(addr, port); + return handle_nsapi_error("Socket::bind(addr, port)", info->socket().bind(tmp)); + } else { + cmd_printf("Socket::bind(%" PRId32 ")\r\n", port); + SocketAddress tmp(NULL, port); + return handle_nsapi_error("Socket::bind(port)", info->socket().bind(tmp)); + } + + } else if (COMMAND_IS("set_blocking")) { + bool val; + if (!cmd_parameter_bool(argc, argv, "set_blocking", &val)) { + cmd_printf("Need boolean value"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + info->set_blocking(val); + return CMDLINE_RETCODE_SUCCESS; + + } else if (COMMAND_IS("set_timeout")) { + int32_t ms; + if (!cmd_parameter_int(argc, argv, "set_timeout", &ms)) { + cmd_printf("Need timeout value"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + if (ms == -1) { + info->set_blocking(true); + } else { + info->set_blocking(false); + } + + info->socket().set_timeout(ms); + return CMDLINE_RETCODE_SUCCESS; + + } else if (COMMAND_IS("register_sigio_cb")) { + info->socket().sigio(callback(sigio_handler, info)); + return CMDLINE_RETCODE_SUCCESS; + } + + + /* + * Commands related to UDPSocket: + * sendto, recvfrom + */ + if ((COMMAND_IS("sendto") || COMMAND_IS("recvfrom") || COMMAND_IS("start_udp_receiver_thread") + || COMMAND_IS("last_data_received")) && info->type() != SInfo::UDP) { + cmd_printf("Not UDPSocket\r\n"); + return CMDLINE_RETCODE_FAIL; + } + + if (COMMAND_IS("sendto")) { + return udp_sendto_command_handler(info, argc, argv); + } else if (COMMAND_IS("recvfrom")) { + return udp_recvfrom_command_handler(info, argc, argv); + } else if (COMMAND_IS("start_udp_receiver_thread")) { + return start_udp_receiver_thread(info, argc, argv); + } else if (COMMAND_IS("last_data_received")) { + print_data((const uint8_t *)info->getReceiveBuffer(), info->getDataCount()); + if (info->getPacketSizeArray()) { + int *packetSizes = info->getPacketSizeArray(); + cmd_printf("packet_sizes: "); + for (int i = 0; i < PACKET_SIZE_ARRAY_LEN; i++) { + cmd_printf("%d ", packetSizes[i]); + } + cmd_printf("\r\n"); + } + if (info->getReceiverThread()) { + info->getReceiverThread()->terminate(); + } + thread_clean_up(info); + + return handle_nsapi_error("UDPSocket::last_data_received()", NSAPI_ERROR_OK); + } + + /* + * Commands related to TCPSocket + * connect, send, recv + */ + if ((COMMAND_IS("connect") || COMMAND_IS("recv") + || COMMAND_IS("start_tcp_receiver_thread") || COMMAND_IS("join_tcp_receiver_thread") + || COMMAND_IS("start_bg_traffic_thread") || COMMAND_IS("join_bg_traffic_thread") + || COMMAND_IS("setsockopt_keepalive") || COMMAND_IS("getsockopt_keepalive")) + && info->type() != SInfo::TCP_CLIENT) { + cmd_printf("Not TCPSocket\r\n"); + return CMDLINE_RETCODE_FAIL; + } + + if (COMMAND_IS("connect")) { + char *host; + int32_t port; + + if (!cmd_parameter_val(argc, argv, "connect", &host)) { + cmd_printf("Need host name\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + if (!cmd_parameter_int(argc, argv, host, &port)) { + cmd_printf("Need port number\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + if (strcmp(host, "NULL") == 0) { + host = NULL; + } + + cmd_printf("Host name: %s port: %" PRId32 "\r\n", host, port); + return handle_nsapi_error("TCPSocket::connect()", static_cast(info->socket()).connect(host, port)); + + } else if (COMMAND_IS("send")) { + return tcp_send_command_handler(info, argc, argv); + + } else if (COMMAND_IS("recv")) { + return tcp_recv_command_handler(info, argc, argv); + + } else if (COMMAND_IS("start_tcp_receiver_thread")) { + return start_tcp_receiver_thread(info, argc, argv); + + } else if (COMMAND_IS("join_tcp_receiver_thread")) { + info->getReceiverThread()->join(); + print_data((const uint8_t *)info->getReceiveBuffer(), info->getDataCount()); + cmd_printf("received: %d bytes\r\n", info->getRecvTotal()); + + thread_clean_up(info); + + return CMDLINE_RETCODE_SUCCESS; + + } else if (COMMAND_IS("start_bg_traffic_thread")) { + return start_bg_traffic_thread(info, argc, argv); + + } else if (COMMAND_IS("join_bg_traffic_thread")) { + int bg_thread_success = CMDLINE_RETCODE_SUCCESS; + + if (!info->available()) { // Tells that background thread stumbled to an issue and stopped prematurely + bg_thread_success = CMDLINE_RETCODE_FAIL; + } + + info->setUnavailable(); + info->getReceiverThread()->join(); + thread_clean_up(info); + + return bg_thread_success; + } else if (COMMAND_IS("setsockopt_keepalive")) { + int32_t seconds; + nsapi_error_t ret; + if (!cmd_parameter_int(argc, argv, "setsockopt_keepalive", &seconds)) { + cmd_printf("Need keep-alive value(0-7200seconds)"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + + ret = info->socket().setsockopt(NSAPI_SOCKET, NSAPI_KEEPALIVE, &seconds, sizeof(seconds)); + + return handle_nsapi_error("TCPSocket::setsockopt()", ret); + } else if (COMMAND_IS("getsockopt_keepalive")) { + int32_t optval; + unsigned optlen = sizeof(optval); + nsapi_error_t ret; + + ret = info->socket().getsockopt(NSAPI_SOCKET, NSAPI_KEEPALIVE, &optval, &optlen); + + if (optlen != sizeof(int)) { + return CMDLINE_RETCODE_FAIL; + } + if (ret < 0) { + return handle_nsapi_error("TCPSocket::getsockopt()", ret); + } + return handle_nsapi_size_or_error("TCPSocket::getsockopt()", optval); + } + + /* + * Commands for TCPServer + * listen, accept + */ + if ((COMMAND_IS("listen") || COMMAND_IS("accept")) && info->type() != SInfo::TCP_SERVER) { + cmd_printf("Not TCPServer\r\n"); + return CMDLINE_RETCODE_FAIL; + } + if (COMMAND_IS("listen")) { + int32_t backlog; + if (cmd_parameter_int(argc, argv, "listen", &backlog)) { + return handle_nsapi_error("TCPServer::listen()", static_cast(info->socket()).listen(backlog)); + } else { + return handle_nsapi_error("TCPServer::listen()", static_cast(info->socket()).listen()); + } + + } else if (COMMAND_IS("accept")) { + SocketAddress addr; + int32_t id; + if (!cmd_parameter_int(argc, argv, "accept", &id)) { + cmd_printf("Need new socket id\r\n"); + return CMDLINE_RETCODE_INVALID_PARAMETERS; + } + SInfo *new_info = get_sinfo(id); + if (!new_info) { + cmd_printf("Invalid socket id\r\n"); + return CMDLINE_RETCODE_FAIL; + } + TCPSocket *new_sock = static_cast(&new_info->socket()); + nsapi_error_t ret = static_cast(info->socket()).accept(new_sock, &addr); + if (ret == NSAPI_ERROR_OK) { + cmd_printf("TCPServer::accept() new socket sid: %d connection from %s port %d\r\n", + new_info->id(), addr.get_ip_address(), addr.get_port()); + } + return handle_nsapi_error("TCPServer::accept()", ret); + } + return CMDLINE_RETCODE_INVALID_PARAMETERS; +} + +void print_data(const uint8_t *buf, int len) +{ + switch (printing_mode) { + case PRINT_STRING: + print_data_as_string(buf, len, printing_col_width); + break; + case PRINT_HEX: + print_data_as_hex(buf, len, printing_col_width); + break; + case PRINT_DISABLED: + break; + default: + assert(0); + } +} + +void print_data_as_string(const uint8_t *buf, int len, int col_width) +{ + int printable_bytes; + for (printable_bytes = 0; printable_bytes < len; printable_bytes++) { + if (!isprint(buf[printable_bytes])) { + break; + } + } + + cmd_mutex_lock(); + cmd_printf("string data, printing %d bytes:\r\n", len); + for (int i = 0; i < printable_bytes; i += col_width) { + if (printable_bytes - i > col_width) { + cmd_printf("%04d: %.*s\r\n", i, col_width, buf + i); + } else { + cmd_printf("%04d: %.*s\r\n", i, printable_bytes - i, buf + i); + } + } + cmd_printf("Printed %d bytes\r\n", printable_bytes); + + if (len != printable_bytes) { + cmd_printf("Error! Couldn't print all data. " + "Unprintable character: 0x%02x found at index: %d\r\n", + buf[printable_bytes], printable_bytes); + } + cmd_mutex_unlock(); +} + +void print_data_as_hex(const uint8_t *buf, int len, int col_width) +{ + cmd_mutex_lock(); + cmd_printf("hex data, printing %d bytes:\r\n", len); + for (int i = 0; i < len; i += col_width) { + if (len - i > col_width) { + cmd_printf("%04d: %s:\r\n", i, print_array(buf + i, col_width)); + } else { + cmd_printf("%04d: %s\r\n", i, print_array(buf + i, len - i)); + } + } + cmd_printf("Printed %d bytes\r\n", len); + cmd_mutex_unlock(); +} diff --git a/TEST_APPS/device/socket_app/cmd_socket.h b/TEST_APPS/device/socket_app/cmd_socket.h new file mode 100644 index 00000000000..9e2239c790f --- /dev/null +++ b/TEST_APPS/device/socket_app/cmd_socket.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#ifndef CMD_SOCKET_H_ +#define CMD_SOCKET_H_ + +#include "nsapi_types.h" + +int handle_nsapi_error(const char *function, nsapi_error_t ret); +int handle_nsapi_size_or_error(const char *function, nsapi_size_or_error_t ret); +void cmd_socket_init(void); + +#endif diff --git a/TEST_APPS/device/socket_app/main.cpp b/TEST_APPS/device/socket_app/main.cpp new file mode 100644 index 00000000000..3bc1365613f --- /dev/null +++ b/TEST_APPS/device/socket_app/main.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#include +#include +#include "mbed.h" +#include "mbed-client-cli/ns_cmdline.h" +#include "cmd_ifconfig.h" +#include "cmd_socket.h" + +/** + * Macros for setting console flow control. + */ +#define CONSOLE_FLOWCONTROL_RTS 1 +#define CONSOLE_FLOWCONTROL_CTS 2 +#define CONSOLE_FLOWCONTROL_RTSCTS 3 +#define mbed_console_concat_(x) CONSOLE_FLOWCONTROL_##x +#define mbed_console_concat(x) mbed_console_concat_(x) +#define CONSOLE_FLOWCONTROL mbed_console_concat(MBED_CONF_TARGET_CONSOLE_UART_FLOW_CONTROL) + +#define SERIAL_CONSOLE_BAUD_RATE 115200 + +void cmd_ready_cb(int retcode) +{ + cmd_next(retcode); +} + +void wrap_printf(const char *f, va_list a) +{ + vprintf(f, a); +} + +int main() +{ + cmd_init(&wrap_printf); + cmd_ifconfig_init(); + cmd_socket_init(); + + int c; + while ((c = getchar()) != EOF) { + cmd_char_input(c); + } + return 0; +} + +FileHandle *mbed::mbed_override_console(int) +{ + static UARTSerial console(STDIO_UART_TX, STDIO_UART_RX, SERIAL_CONSOLE_BAUD_RATE); +#if CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTS + console.set_flow_control(SerialBase::RTS, STDIO_UART_RTS, NC); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_CTS + console.set_flow_control(SerialBase::CTS, NC, STDIO_UART_CTS); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTSCTS + console.set_flow_control(SerialBase::RTSCTS, STDIO_UART_RTS, STDIO_UART_CTS); +#endif + return &console; +} diff --git a/TEST_APPS/device/socket_app/mbed_app.json b/TEST_APPS/device/socket_app/mbed_app.json new file mode 100644 index 00000000000..a1829b5e7e7 --- /dev/null +++ b/TEST_APPS/device/socket_app/mbed_app.json @@ -0,0 +1,22 @@ +{ + "macros": [ + "MEM_ALLOC=malloc", + "MEM_FREE=free", + "MBED_HEAP_STATS_ENABLED=1", + "MBED_MEM_TRACING_ENABLED" + ], + "target_overrides": { + "*": { + "target.features_add": ["LWIP", "COMMON_PAL"], + "mbed-trace.enable": 1, + "platform.stdio-baud-rate": 115200, + "platform.stdio-convert-newlines": true, + "platform.stdio-buffered-serial": true, + "platform.stdio-flush-at-exit": true, + "drivers.uart-serial-rxbuf-size": 768 + }, + "UBLOX_EVK_ODIN_W2" : { + "target.device_has_remove": ["EMAC"] + } + } +} diff --git a/TEST_APPS/device/socket_app/strconv.c b/TEST_APPS/device/socket_app/strconv.c new file mode 100644 index 00000000000..720cf446831 --- /dev/null +++ b/TEST_APPS/device/socket_app/strconv.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#include +#include +#include +#include + +#include "ip6string.h" +#include "strconv.h" + + +int8_t strtohex(uint8_t *out, const char *str, int8_t max_length) +{ + int8_t i = 0; + char *rd = (char *)str; + uint8_t *wr = out; + while (*rd != 0) { + if (i > max_length) { + break; + } + *wr++ = strtoul(rd, &rd, 16); + while (!isxdigit((int)*rd) && *rd != 0) { + rd++; //skip some invalid characters + } + i++; + } + return i; +} + +int hexstr_to_bytes_inplace(char *str) +{ + int16_t len, i, j; + if (str == NULL) { + return -1; + } + len = strlen(str); + if (len < 2 || (len + 1) % 3 != 0) { + return -1; + } + for (i = 0, j = 0; i < len; i += 3, ++j) { + str[j] = (char)strtol(str + i, 0, 16); + } + return j; +} + +// convert hex string (eg. "76 ab ff") to binary array +int string_to_bytes(const char *str, uint8_t *buf, int bytes) +{ + int len = strlen(str); + if (len <= (3 * bytes - 1)) { + int i; + for (i = 0; i < bytes; i++) { + if (i * 3 < len) { + buf[i] = (uint8_t)strtoul(str + i * 3, 0, 16); + } else { + buf[i] = 0; + } + } + return 0; + } + return -1; +} + +int16_t hextostr(const uint8_t *buf, uint16_t buf_length, char *out, int16_t out_length, char delimiter) +{ + int16_t outLeft = out_length; + int16_t arrLeft = buf_length; + const uint8_t *rd = buf; + int retcode = 0; + char *wr = out; + while (arrLeft > 0 && outLeft > 0) { + retcode = snprintf(wr, outLeft, "%02x", *rd); + + if (retcode <= 0 || retcode >= outLeft) { + break; + } + outLeft -= retcode; + wr += retcode; + arrLeft --; + rd++; + if (delimiter && arrLeft > 0 && outLeft > 0) { + *wr++ = delimiter; + outLeft--; + *wr = 0; + } + } + return (int16_t)(wr - out); +} +int replace_hexdata(char *str) +{ + char *ptr = str; + if (str == NULL) { + return 0; + } + while (*ptr) { + if (ptr[0] == '\\') { + if (ptr[1] == 'n') { + ptr[0] = 0x0a; + memmove(ptr + 1, ptr + 2, strlen(ptr + 2) + 1); + } else if (ptr[1] == 'r') { + ptr[0] = 0x0d; + memmove(ptr + 1, ptr + 2, strlen(ptr + 2) + 1); + } else if (ptr[1] == 'x') { + char *end; + ptr[0] = (char)strtoul(ptr + 2, &end, 16); + memmove(ptr + 1, end, strlen(end) + 1); + } else if (isdigit((int)ptr[1])) { + char *end; + ptr[0] = strtoul(ptr + 1, &end, 10); + memmove(ptr + 1, end, strlen(end) + 1); + } + } + ptr++; + } + return ptr - str; +} +bool is_visible(uint8_t *buf, int len) +{ + while (len--) { + if (!isalnum(*buf) && *buf != ' ') { + return false; + } + buf++; + } + return true; +} + +char *strdupl(const char *str) +{ + if (!str) { + return NULL; + } + char *p = malloc(strlen(str) + 1); + if (!p) { + return p; + } + strcpy(p, str); + return p; +} +int strnlen_(const char *s, int n) +{ + char *end = memchr(s, 0, n); + return end ? end - s : n; +} +char *strndupl(const char *s, int n) +{ + char *p = NULL; + int len = strnlen_(s, n); + p = malloc(len + 1); + if (!p) { + return p; + } + p[len] = 0; + return memcpy(p, s, len); +} +int strnicmp_(char const *a, char const *b, int n) +{ + for (; (n > 0 && *a != 0 && *b != 0) ; a++, b++, n--) { + int d = tolower((int) * a) - tolower((int) * b); + if (d != 0 || !*a) { + return d; + } + } + return 0; +} + + +/* HELPING PRINT FUNCTIONS for cmd_printf */ + +static inline uint8_t context_split_mask(uint_fast8_t split_value) +{ + return (uint8_t) - (0x100u >> split_value); +} + +static uint8_t *bitcopy(uint8_t *restrict dst, const uint8_t *restrict src, uint_fast8_t bits) +{ + uint_fast8_t bytes = bits / 8; + bits %= 8; + + if (bytes) { + dst = (uint8_t *) memcpy(dst, src, bytes) + bytes; + src += bytes; + } + + if (bits) { + uint_fast8_t split_bit = context_split_mask(bits); + *dst = (*src & split_bit) | (*dst & ~ split_bit); + } + + return dst; +} + +char tmp_print_buffer[128] = {0}; + +char *print_ipv6(const void *addr_ptr) +{ + ip6tos(addr_ptr, tmp_print_buffer); + return tmp_print_buffer; +} +char *print_ipv6_prefix(const uint8_t *prefix, uint8_t prefix_len) +{ + char *str = tmp_print_buffer; + int retval; + char tmp[40]; + uint8_t addr[16] = {0}; + + if (prefix_len != 0) { + if (prefix == NULL || prefix_len > 128) { + return ""; + } + bitcopy(addr, prefix, prefix_len); + } + + ip6tos(addr, tmp); + retval = snprintf(str, 128, "%s/%u", tmp, prefix_len); + if (retval <= 0) { + return ""; + } + return str; +} +char *print_array(const uint8_t *buf, uint16_t len) +{ + int16_t retcode = hextostr(buf, len, tmp_print_buffer, 128, ':'); + if (retcode > 0) { + //yeah, something is converted + } + return tmp_print_buffer; +} + diff --git a/TEST_APPS/device/socket_app/strconv.h b/TEST_APPS/device/socket_app/strconv.h new file mode 100644 index 00000000000..4d0511c086f --- /dev/null +++ b/TEST_APPS/device/socket_app/strconv.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ +#ifndef STRCONVERSION_H +#define STRCONVERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ns_types.h" + +/** Convert hex string to binary array + * e.g. + * char str[] = {30, 31, 32, 33}; + * uint8_t buffer[4]; + * strtohex( buffer, str, 4 ); + */ +int8_t strtohex(uint8_t *out, const char *str, int8_t max_length); + +/** Convert space separated hex string (eg. "76 ab ff") to binary array. + */ +int string_to_bytes(const char *str, uint8_t *buf, int bytes); + +/** Convert a colon/space separated hex string (eg. "76:ab:ff") to binary + * array (inplace) returning the number of the bytes in the array. + */ +int hexstr_to_bytes_inplace(char *str); + +/** Convert hex array to string + */ +int16_t hextostr(const uint8_t *buf, uint16_t buf_length, char *out, int16_t out_length, char delimiter); +/** Replace hex characters from string + * e.g. + * "hello\\n" -> "hello\n" + * "hello\\r" -> "hello\r" + * "val: \\10" -> "val: \x0a" //integer + * "val: \\x10" -> "val: \x10" //hex value + * @param str string that will be replaced + * @return length + */ +int replace_hexdata(char *str); +/** + * check if array contains only visible characters + * = isalpha | isdigit + */ +bool is_visible(uint8_t *buf, int len); + +/** Convert ipv6 address to string format + */ +char *print_ipv6(const void *addr_ptr); +/** Convert ipv6 prefix to string format + */ +char *print_ipv6_prefix(const uint8_t *prefix, uint8_t prefix_len); +/** Convert binary array to string format and return static results + */ +char *print_array(const uint8_t *buf, uint16_t len); +/** The strdupl() function shall return a pointer to a new string, + * which is a duplicate of the string pointed to by s1. The returned pointer can be passed to free(). + * A null pointer is returned if the new string cannot be created. + * strdupl are same than linux strdup, but this way we avoid to duplicate reference in linux + */ +char *strdupl(const char *str); +/** The strdup() function returns a pointer to a new string which is a duplicate of the string. + * Memory for the new string is obtained with malloc(3), and can be freed with free(3). + * The strndup() function is similar, but only copies at most n bytes. If s is longer than n, + * only n bytes are copied, and a terminating null byte ('\0') is added. + */ +char *strndupl(const char *s, int n); +/** strnlen - determine the length of a fixed-size string + * The strnlen() function returns the number of bytes in the string pointed to by s, excluding the terminating null bye ('\0'), but at most maxlen. + * In doing this, strnlen() looks only at the first maxlen bytes at s and never beyond s+maxlen. + * The strnlen() function returns strlen(s), if that is less than maxlen, or maxlen if there is no null byte ('\0') + * among the first maxlen bytes pointed to by s. + */ +int strnlen_(const char *s, int n); +/** strnicmp compares a and b without sensitivity to case. + * All alphabetic characters in the two arguments a and b are converted to lowercase before the comparison. + */ +int strnicmp_(char const *a, char const *b, int n); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/TEST_APPS/icetea_plugins/__init__.py b/TEST_APPS/icetea_plugins/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/TEST_APPS/icetea_plugins/ip_test_parsers.py b/TEST_APPS/icetea_plugins/ip_test_parsers.py new file mode 100644 index 00000000000..79794be0a84 --- /dev/null +++ b/TEST_APPS/icetea_plugins/ip_test_parsers.py @@ -0,0 +1,157 @@ +""" +Copyright 2018 ARM Limited +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. +""" +import re +import time +from collections import OrderedDict +from datetime import datetime + +from icetea_lib.Plugin.PluginBase import PluginBase + + +class IpTestParsers(PluginBase): + # constructor + def __init__(self): + super(IpTestParsers, self).__init__() + + def get_parsers(self): + return { + 'ifconfig': self.__ifconfigParser, + 'ifup': self.__ifconfigParser, + 'ethup': self.__ifconfigParser, + 'dut1': self.__ifconfigParser, + 'dut2': self.__ifconfigParser, + 'dut3': self.__ifconfigParser, + 'dut4': self.__ifconfigParser, + 'dut5': self.__ifconfigParser, + 'dut6': self.__ifconfigParser, + 'socket': self.__mbedossocketParser, + 'ticker': self.__ticker_parser + } + + # socket command for mbedos + def __mbedossocketParser(self, response): + results = {'socket_id': None, + 'data_type': None, + 'data': "", + 'printed_bytes': None, + 'sent_bytes': None, + 'received_bytes': None, + 'address': None, + 'port': None, + 'loop_id': None + } + respLines = response.lines + part = None + for line in respLines: + ret = PluginBase.find_one(line, ".*sid: ([0-9]+)") + if ret is not False: + results['socket_id'] = ret + + ret = PluginBase.find_one(line, ".*(hex|string) data, printing .* bytes:") + if ret is not False: + results['data_type'] = ret + + ret = PluginBase.find_one(line, ".*data, printing (.*) bytes:") + if ret is not False: + part = "data" + + ret = PluginBase.find_one(line, "^Printed (.*) bytes$") + if ret is not False: + results['printed_bytes'] = int(ret) + part = None + + if part == "data": + ret = PluginBase.find_one(line, "^\d{4}: (.*)$") + if ret is not False: + results['data'] = results['data'] + ret + + ret = PluginBase.find_one(line, ".*sent: ([0-9]+) bytes") + if ret is not False: + results['sent_bytes'] = int(ret) + + ret = PluginBase.find_one(line, ".*received: ([0-9]+) bytes") + if ret is not False: + results['received_bytes'] = int(ret) + + ret = PluginBase.find_one(line, ".*address: ([0-9a-fxA-F:.]+)") + if ret is not False: + results['address'] = ret + + ret = PluginBase.find_one(line, ".*port: ([0-9]+)") + if ret is not False: + results['port'] = ret + + ret = PluginBase.find_one(line, ".*lid: ([0-9]+)") + if ret is not False: + results['loop_id'] = ret + + return results + + # response parser for ifup + def __ifconfigParser(self, response): + results = {} + lines = response.traces + part = None + + results['address'] = { + 'll': '', + 'globals': [], + 'ipv4': None, + 'ipv6': [] + } + + for line in lines: + # print "ifcfgparser: %s" % line + match = re.search('IPv6 if addr', line) + if match: + part = "address" + match = re.search('IPv4 if addr', line) + if match: + part = "address" + match = re.search('MAC-48\:[\W]{1,}(.*)', line) + if match: + mac48 = match.group(1) + # Validate the MAC-48 syntax as well + match = re.search("([0-9a-fA-F]{2}:??){5}([0-9a-fA-F]{2})", mac48) + if match: + results['MAC'] = mac48 + + if part == "address": + match = re.search('.*\[(\d)\]:\W([abcdefg\d\:]{5,})', line) + if match: + addr = match.group(2) + if re.search('^fe80\:\:', addr): + results['address']['ll'] = addr + else: + results['address']["globals"].append(addr) + + match = re.search('\[(\d)\]:\W([a-fA-F\d\:]{5,})', line) + if match: + results['address']['ipv6'].append(match.group(2)) + + match = re.search('(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$', line) + if match: + results['address']['ipv4'] = match.group(1) + + return results + + def __ticker_parser(self, response): + results = {} + respLines = response.lines + for line in respLines: + ret = PluginBase.find_one(line, 'Ticker id: ([0-9]+)') + if ret is not False: + results['ticker_id'] = ret + return results diff --git a/TEST_APPS/icetea_plugins/plugins_to_load.py b/TEST_APPS/icetea_plugins/plugins_to_load.py new file mode 100644 index 00000000000..1322fbcc466 --- /dev/null +++ b/TEST_APPS/icetea_plugins/plugins_to_load.py @@ -0,0 +1,19 @@ +""" +Copyright 2018 ARM Limited +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. +""" +from ip_test_parsers import IpTestParsers + +plugins_to_load = { + "ip_test_parsers": IpTestParsers +} diff --git a/TEST_APPS/readme.md b/TEST_APPS/readme.md new file mode 100644 index 00000000000..178be60d618 --- /dev/null +++ b/TEST_APPS/readme.md @@ -0,0 +1,93 @@ +## Running Icetea tests located under mbed-os + +### Structure + +mbed-os has a folder called TEST_APPS that contains everything related to Icetea testing. +There are currently 3 folders: + +- device - contains all the different test applications you can flash to your board +- icetea-plugins - contains plugins that are being used by some of the testcases, needed for the test execution +- testcases - contains Icetea testcases written in Python + +The testcases depends on test applications + +### Preparing your work environment + +#### Prerequisities + +You need Icetea and mbed-cli that supports Icetea, installed. + +#### Selecting the network interface to use + +Depending on a device, there might be a default network interface type defined in the mbed-os/targets/targets.json, which is used to locate a test-config file by default. +If there is not, or you want to use a different interface than the default, you need to provide a relevant test-config -file to the mbed test with --test-config option. +The test-config file contains the necessary information for the test application, there are some test-config files located under mbed-os/tools/test-configs. +Devices which have their network drivers residing inside mbed-os can use generic test-configs like HeapBlockDeviceAndEthernetInterface.json and HeapBlockDeviceAndWifiInterface.json. Otherwise you need to use a device specific test-config. + +### Running the tests + +Now that the interface has been selected you can run the icetea tests from the mbed-os root on your command line by + +`>mbed test -m -t --icetea` + +This command will compile the mbed-os, then compiles the test applications, creates a test suite and then starts running the tests. + +If you want only to run some specific tests, you can use the -n -option. You can list multiple tests by separating them by comma (,). + +`>mbed test -m -t --icetea -n test1,test2` + +#### Running the tests with specifig test-config + +Some devices may offer multiple network interfaces to operate with. For example, UBLOX_EVK_ODIN_W2 offers ethernet and Wi-Fi capabilities. +The tests can be run for either one of those using already existing test-config -files. + +To run the tests with Wi-Fi interface: +`>mbed test -m UBLOX_EVK_ODIN_W2 -t --icetea --test-config tools/test-configs/HeapBlockDeviceAndWifiInterface.json` + +To run the tests with ethernet interface: +`>mbed test -m UBLOX_EVK_ODIN_W2 -t --icetea --test-config tools/test-configs/HeapBlockDeviceAndEthernetInterface.json` + +#### Providing Wi-Fi access point information + +If you are using Wi-Fi interface for running the tests, you need to provide also information about the used access point. +The information can be provided in the used test-config file. + +Example of access point information: +``` + "target_overrides": { + "*": { + "target.network-default-interface-type": "WIFI", + "nsapi.default-wifi-ssid": "\"ssid\"", + "nsapi.default-wifi-password": "\"password\"", + "nsapi.default-wifi-security": "WPA_WPA2" + } + } +``` + +### Test results + +Icetea prints the results from the test run to the command line, and the final result looks similar to this. + +``` ++--------------------------------+---------+-------------+-------------+-----------+----------+ +| Testcase | Verdict | Fail Reason | Skip Reason | platforms | duration | ++--------------------------------+---------+-------------+-------------+-----------+----------+ +| test_cmdline | pass | | | K64F | 8.555 | +| UDPSOCKET_BIND_PORT | pass | | | K64F | 19.614 | +| TCPSOCKET_BIND_PORT | pass | | | K64F | 15.852 | +| TCPSERVER_ACCEPT | pass | | | K64F | 41.629 | +| TCPSOCKET_ECHOTEST_BURST_SHORT | pass | | | K64F | 19.926 | ++--------------------------------+---------+-------------+-------------+-----------+----------+ ++---------------+----------------+ +| Summary | | ++---------------+----------------+ +| Final Verdict | PASS | +| count | 5 | +| passrate | 100.00 % | +| pass | 5 | +| Duration | 0:01:45.576000 | ++---------------+----------------+ +``` + +The results from the tests can also be found in the mbed-os/log folder. +You probably want to add the log folder to your .mbedignore file to prevent issues with build commands becoming too long over the time. diff --git a/TEST_APPS/testcases/__init__.py b/TEST_APPS/testcases/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/TEST_APPS/testcases/example/__init__.py b/TEST_APPS/testcases/example/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/TEST_APPS/testcases/example/test_cmdline.py b/TEST_APPS/testcases/example/test_cmdline.py new file mode 100644 index 00000000000..52a22e92027 --- /dev/null +++ b/TEST_APPS/testcases/example/test_cmdline.py @@ -0,0 +1,50 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from icetea_lib.bench import Bench + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="test_cmdline", + title="Smoke test for command line interface", + status="released", + purpose="Verify Command Line Interface", + component=["cmdline"], + type="smoke", + requirements={ + "duts": { + '*': { + "count": 1, + "type": "hardware", + "application": { + "name": "TEST_APPS-device-exampleapp" + } + }, + "1": {"nick": "dut1"}, + } + } + ) + + def setup(self): + pass + + def case(self): + self.command("dut1", "echo hello world") + self.command("dut1", "help") + + def teardown(self): + pass diff --git a/TEST_APPS/testcases/netsocket/SOCKET_BIND_PORT.py b/TEST_APPS/testcases/netsocket/SOCKET_BIND_PORT.py new file mode 100644 index 00000000000..4c601e770d6 --- /dev/null +++ b/TEST_APPS/testcases/netsocket/SOCKET_BIND_PORT.py @@ -0,0 +1,71 @@ +""" +Copyright 2018 ARM Limited +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. +""" +from icetea_lib.bench import Bench +from icetea_lib.tools import test_case + + +class MultipleTestcase(Bench): + def __init__(self, **kwargs): + testcase_args = { + 'status': "released", + 'component': ["mbed-os", "netsocket"], + 'type': "smoke", + 'subtype': "socket", + 'requirements': { + "duts": { + "*": { + "count": 1, + "type": "hardware", + "application": {"name": "TEST_APPS-device-socket_app"} + }, + "1": {"nick": "dut1"}, + } + } + } + + testcase_args.update(kwargs) + Bench.__init__(self, **testcase_args) + + def setup(self): + self.command("dut1", "ifup") + + def socket_bind_port(self, socket_type): + response = self.command("dut1", "socket new " + socket_type) + socket_id = int(response.parsed['socket_id']) + + self.command("dut1", "socket " + str(socket_id) + " open") + + self.command("dut1", "socket " + str(socket_id) + " bind port 1024") + + self.command("dut1", "socket " + str(socket_id) + " delete") + + def teardown(self): + self.command("dut1", "ifdown") + + +@test_case(MultipleTestcase, + name="TCPSOCKET_BIND_PORT", + title="tcpsocket open and bind port", + purpose="Verify TCPSocket can be created, opened and port binded") +def test1(self): + self.socket_bind_port("TCPSocket") + + +@test_case(MultipleTestcase, + name="UDPSOCKET_BIND_PORT", + title="udpsocket open and bind port", + purpose="Verify UDPSocket can be created, opened and port binded") +def test2(self): + self.socket_bind_port("UDPSocket") diff --git a/TEST_APPS/testcases/netsocket/TCPSERVER_ACCEPT.py b/TEST_APPS/testcases/netsocket/TCPSERVER_ACCEPT.py new file mode 100644 index 00000000000..e975d683596 --- /dev/null +++ b/TEST_APPS/testcases/netsocket/TCPSERVER_ACCEPT.py @@ -0,0 +1,103 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +import threading +import time + +from icetea_lib.TestStepError import TestStepFail +from icetea_lib.bench import Bench +from interface import interfaceUp, interfaceDown + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="TCPSERVER_ACCEPT", + title="TCPSERVER_ACCEPT", + purpose="Test that TCPServer::bind(), TCPServer::listen() and TCPServer::accept() works", + status="released", + component=["mbed-os", "netsocket"], + author="Juha Ylinen ", + type="smoke", + subtype="socket", + requirements={ + "duts": { + '*': { # requirements for all nodes + "count": 2, + "type": "hardware", + "application": {"name": "TEST_APPS-device-socket_app"} + }, + "1": {"nick": "dut1"}, + "2": {"nick": "dut2"} + } + } + ) + + def setup(self): + interface = interfaceUp(self, ["dut1"]) + self.server_ip = interface["dut1"]["ip"] + interface = interfaceUp(self, ["dut2"]) + self.client_ip = interface["dut2"]["ip"] + + def clientThread(self): + self.logger.info("Starting") + time.sleep(5) # wait accept from server + self.command("dut2", "socket " + str(self.client_socket_id) + " open") + self.command("dut2", "socket " + str(self.client_socket_id) + " connect " + str(self.server_ip) + " " + str( + self.used_port)) + + def case(self): + self.used_port = 2000 + + response = self.command("dut1", "socket new TCPServer") + server_base_socket_id = int(response.parsed['socket_id']) + + self.command("dut1", "socket " + str(server_base_socket_id) + " open") + self.command("dut1", "socket " + str(server_base_socket_id) + " bind port " + str(self.used_port)) + self.command("dut1", "socket " + str(server_base_socket_id) + " listen") + + response = self.command("dut1", "socket new TCPSocket") + server_socket_id = int(response.parsed['socket_id']) + self.command("dut1", "socket " + str(server_socket_id) + " open") + + response = self.command("dut2", "socket new TCPSocket") + zero = response.timedelta + self.client_socket_id = int(response.parsed['socket_id']) + + # Create a thread which calls client connect() + t = threading.Thread(name='clientThread', target=self.clientThread) + t.start() + + wait = 5 + response = self.command("dut1", "socket " + str(server_base_socket_id) + " accept " + str(server_socket_id)) + response.verify_response_duration(expected=wait, zero=zero, threshold_percent=10, break_in_fail=True) + socket_id = int(response.parsed['socket_id']) + + t.join() + self.command("dut1", "socket " + str(socket_id) + " send hello") + + response = self.command("dut2", "socket " + str(self.client_socket_id) + " recv 5") + data = response.parsed['data'].replace(":", "") + + if data != "hello": + raise TestStepFail("Received data doesn't match the sent data") + + self.command("dut1", "socket " + str(server_socket_id) + " delete") + self.command("dut1", "socket " + str(server_base_socket_id) + " delete") + self.command("dut2", "socket " + str(self.client_socket_id) + " delete") + + def teardown(self): + interfaceDown(self, ["dut1"]) + interfaceDown(self, ["dut2"]) diff --git a/TEST_APPS/testcases/netsocket/TCPSOCKET_ECHOTEST_BURST_SHORT.py b/TEST_APPS/testcases/netsocket/TCPSOCKET_ECHOTEST_BURST_SHORT.py new file mode 100644 index 00000000000..3ac98da062e --- /dev/null +++ b/TEST_APPS/testcases/netsocket/TCPSOCKET_ECHOTEST_BURST_SHORT.py @@ -0,0 +1,78 @@ +""" +Copyright 2018 ARM Limited +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. +""" +import string + +from icetea_lib.Randomize.randomize import Randomize +from icetea_lib.bench import Bench, TestStepFail + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="TCPSOCKET_ECHOTEST_BURST_SHORT", + title="TCPSOCKET_ECHOTEST_BURST_SHORT", + purpose="Verify that TCPSocket can send burst of packets to echo server and read incoming packets", + status="released", + component=["mbed-os", "netsocket"], + author="Juha Ylinen ", + type="smoke", + subtype="socket", + requirements={ + "duts": { + '*': { # requirements for all nodes + "count": 1, + "type": "hardware", + "application": { + "name": "TEST_APPS-device-socket_app" + } + }, + "1": {"nick": "dut1"}, + } + } + ) + + def setup(self): + self.command("dut1", "ifup") + + def case(self): + response = self.command("dut1", "socket new TCPSocket") + socket_id = int(response.parsed['socket_id']) + + self.command("dut1", "socket " + str(socket_id) + " open") + self.command("dut1", "socket " + str(socket_id) + " connect echo.mbedcloudtesting.com 7") + + for i in range(2): + sentData = "" + for size in (100, 200, 300, 120, 500): + packet = Randomize.random_string(max_len=size, min_len=size, chars=string.ascii_uppercase) + sentData += packet + response = self.command("dut1", "socket " + str(socket_id) + " send " + str(packet)) + response.verify_trace("TCPSocket::send() returned: " + str(size)) + + received = 0 + data = "" + totalSize = 1220 + while received < totalSize: + response = self.command("dut1", "socket " + str(socket_id) + " recv " + str(totalSize)) + data += response.parsed['data'].replace(":", "") + received += int(response.parsed['received_bytes']) + + if data != sentData: + raise TestStepFail("Received data doesn't match the sent data") + + self.command("dut1", "socket " + str(socket_id) + " delete") + + def teardown(self): + self.command("dut1", "ifdown") diff --git a/TEST_APPS/testcases/netsocket/__init__.py b/TEST_APPS/testcases/netsocket/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/TEST_APPS/testcases/netsocket/interface.py b/TEST_APPS/testcases/netsocket/interface.py new file mode 100644 index 00000000000..db81636ec3c --- /dev/null +++ b/TEST_APPS/testcases/netsocket/interface.py @@ -0,0 +1,43 @@ +""" +Copyright 2018 ARM Limited +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. +""" +from icetea_lib.TestStepError import TestStepFail + +''' +This interface script is intended to be a common library to be used in testcase scripts by testers. +It delegates setUp and tearDown functions with different provided network interface types using setUp() and tearDown() methods. +''' + + +def interfaceUp(tc, duts): + interfaces = {} + for dut in duts: + interface = {dut: {"ipv4": None, "ipv6": None}} + + resp = tc.command("%s" % dut, "ifup") + + ip = interface[dut]["ip"] = interface[dut]["ipv4"] = resp.parsed["address"]["ipv4"] + if not ip: + if resp.parsed["address"]["ipv6"]: + ip = interface[dut]["ip"] = interface[dut]["ipv6"] = resp.parsed["address"]["ipv6"][0] + if not ip: + raise TestStepFail("Failed to parse IP address") + + interfaces.update(interface) + return interfaces + + +def interfaceDown(tc, duts): + for dut in duts: + tc.command(dut, "ifdown") diff --git a/astyle-branch.out b/astyle-branch.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/features/frameworks/mbed-client-cli/mbed-client-cli/ns_cmdline.h b/features/frameworks/mbed-client-cli/mbed-client-cli/ns_cmdline.h new file mode 100644 index 00000000000..09a0e68606e --- /dev/null +++ b/features/frameworks/mbed-client-cli/mbed-client-cli/ns_cmdline.h @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2016 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ + +/** + * \file ns_cmdline.h + * + * Command line library - mbedOS shell + * + * Usage example: + * + * \code + * //simple print function + * void myprint(const char* fmt, va_list ap){ vprintf(fmt, ap); } + * // simple ready cb, which call next command to be execute + * void cmd_ready_cb(int retcode) { cmd_next( retcode ); } + * + * // dummy command with some option + * int cmd_dummy(int argc, char *argv[]){ + * if( cmd_has_option(argc, argv, "o") ) { + * cmd_printf("This is o option"); + * } else { + * return CMDLINE_RETCODE_INVALID_PARAMETERS; + * } + * return CMDLINE_RETCODE_SUCCESS; + *} + * // timer cb ( pseudo-timer-code ) + * void timer_ready_cb(void) { + * cmd_ready(CMDLINE_RETCODE_SUCCESS); + * } + * // long command, which need e.g. some events to finalize command execution + * int cmd_long(int argc, char *argv[] ) { + timer_start( 5000, timer_ready_cb ); + * return CMDLINE_RETCODE_EXCUTING_CONTINUE; + * } + * void main(void) { + * cmd_init( &myprint ); // initialize cmdline with print function + * cmd_set_ready_cb( cmd_ready_cb ); // configure ready cb + * cmd_add("dummy", cmd_dummy, 0, 0); // add one dummy command + * cmd_add("long", cmd_long, 0, 0); // add one dummy command + * //execute dummy and long commands + * cmd_exe( "dymmy;long" ); + * } + * \endcode + */ +#ifndef _CMDLINE_H_ +#define _CMDLINE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#define CMDLINE_RETCODE_COMMAND_BUSY 2 //!< Command Busy +#define CMDLINE_RETCODE_EXCUTING_CONTINUE 1 //!< Execution continue in background +#define CMDLINE_RETCODE_SUCCESS 0 //!< Execution Success +#define CMDLINE_RETCODE_FAIL -1 //!< Execution Fail +#define CMDLINE_RETCODE_INVALID_PARAMETERS -2 //!< Command parameters was incorrect +#define CMDLINE_RETCODE_COMMAND_NOT_IMPLEMENTED -3 //!< Command not implemented +#define CMDLINE_RETCODE_COMMAND_CB_MISSING -4 //!< Command callback function missing +#define CMDLINE_RETCODE_COMMAND_NOT_FOUND -5 //!< Command not found + +/** + * typedef for print functions + */ +typedef void (cmd_print_t)(const char *, va_list); +/** + * Initialize cmdline class. + * This is command line editor without any commands. Application + * needs to add commands that should be enabled. + * usage e.g. + * \code + cmd_init( &default_cmd_response_out ); + * \endcode + * \param outf console printing function (like vprintf) + */ +void cmd_init(cmd_print_t *outf); +/** Command ready function for __special__ cases. + * This need to be call if command implementation return CMDLINE_RETCODE_EXECUTING_CONTINUE + * because there is some background stuff ongoing before command is finally completed. + * Normally there is some event, which call cmd_ready(). + * \param retcode return code for command + */ +void cmd_ready(int retcode); +/** typedef for ready cb function */ +typedef void (cmd_ready_cb_f)(int); +/** + * Configure cb which will be called after commands are executed + * or cmd_ready is called + * \param cb callback function for command ready + */ +void cmd_set_ready_cb(cmd_ready_cb_f *cb); +/** + * execute next command if any + * \param retcode last command return value + */ +void cmd_next(int retcode); +/** Free cmd class */ +void cmd_free(void); +/** Reset cmdline to default values + * detach external commands, delete all variables and aliases + */ +void cmd_reset(void); +/** Configure command history size (default 32) + * \param max maximum history size + * max > 0 -> configure new value + * max = 0 -> just return current value + * \return current history max-size + */ +uint8_t cmd_history_size(uint8_t max); +/** command line print function + * This function should be used when user want to print something to the console + * \param fmt console print function (like printf) + */ +#if defined(__GNUC__) || defined(__CC_ARM) +void cmd_printf(const char *fmt, ...) __attribute__ ((__format__(__printf__, 1, 2))); +#else +void cmd_printf(const char *fmt, ...); +#endif +/** command line print function + * This function should be used when user want to print something to the console with vprintf functionality + * \param fmt The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of zero or more directives. + * \param ap list of parameters needed by format string. This must correspond properly with the conversion specifier. + */ +#if defined(__GNUC__) || defined(__CC_ARM) +void cmd_vprintf(const char *fmt, va_list ap) __attribute__ ((__format__(__printf__, 1, 0))); +#else +void cmd_vprintf(const char *fmt, va_list ap); +#endif +/** Reconfigure default cmdline out function (cmd_printf) + * \param outf select console print function + */ +void cmd_out_func(cmd_print_t *outf); +/** Configure function, which will be called when Ctrl+A is pressed + * \param sohf control function which called every time when user input control keys + */ +void cmd_ctrl_func(void (*sohf)(uint8_t c)); +/** + * Configure mutex wait function + * By default, cmd_printf calls may not be thread safe, depending on the implementation of the used output. + * This can be used to set a callback function that will be called before each cmd_printf call. + * The specific implementation is up to the application developer, but simple mutex locking is assumed. + */ +void cmd_mutex_wait_func(void (*mutex_wait_f)(void)); +/** + * Configure mutex wait function + * By default, cmd_printf calls may not be thread safe, depending on the implementation of the used output. + * This can be used to set a callback function that will be called after each cmd_printf call. + * The specific implementation is up to the application developer, but simple mutex locking is assumed. + */ +void cmd_mutex_release_func(void (*mutex_release_f)(void)); +/** + * Retrieve output mutex lock + * This can be used to retrieve the output mutex when multiple cmd_printf/cmd_vprintf calls must be + * guaranteed to be grouped together in a thread safe manner. Must be released by a following call to + * cmd_mutex_unlock() + * For example: + * * \code + * cmd_mutex_lock(); + for (i = 0; i < 10; i++) { + cmd_printf("%02x ", i); + } + // without locking a print from another thread could happen here + cmd_printf("\r\n); + cmd_mutex_unlock(); + * \endcode + * Exact behaviour depends on the implementation of the configured mutex, + * but counting mutexes are required. + */ +void cmd_mutex_lock(void); +/** + * Release output mutex lock + * This can be used to release the output mutex once it has been retrieved with cmd_mutex_lock() + * Exact behaviour depends on the implementation of the configured mutex, + * but counting mutexes are required. + */ +void cmd_mutex_unlock(void); +/** Refresh output */ +void cmd_output(void); +/** default cmd response function, use stdout + * \param fmt The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of zero or more directives. + * \param ap list of parameters needed by format string. This must correspond properly with the conversion specifier. + */ +void default_cmd_response_out(const char *fmt, va_list ap); +/** Initialize screen */ +void cmd_init_screen(void); +/** Get echo state + * \return true if echo is on otherwise false + */ +bool cmd_echo_state(void); +/** Echo off */ +void cmd_echo_off(void); +/** Echo on */ +void cmd_echo_on(void); +/** Enter character to console. + * insert key pressess to cmdline called from main loop of application + * \param u_data char to be added to console + */ +void cmd_char_input(int16_t u_data); +/* + * Set the passthrough mode callback function. In passthrough mode normal command input handling is skipped and any + * received characters are passed to the passthrough callback function. Setting this to null will disable passthrough mode. + * \param passthrough_fnc The passthrough callback function + */ +typedef void (*input_passthrough_func_t)(uint8_t c); +void cmd_input_passthrough_func(input_passthrough_func_t passthrough_fnc); + +/* Methods used for adding and handling of commands and aliases + */ + +/** Callback called when your command is run. + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + */ +typedef int (cmd_run_cb)(int argc, char *argv[]); +/** Add command to intepreter + * \param name command string + * \param callback This function is called when command line start executing + * \param info Command short description which is visible in help command, or null if not in use + * \param man Help page for this command. This is shown when executing command with invalid parameters or command with --help parameter. Can be null if not in use. + */ +void cmd_add(const char *name, cmd_run_cb *callback, const char *info, const char *man); + +/** delete command from intepreter + * \param name command to be delete + */ +void cmd_delete(const char *name); +/** Command executer. + * Command executer, which split&push command(s) to the buffer and + * start executing commands in cmd tasklet. + * if not, execute command directly. + * If command implementation returns CMDLINE_RETCODE_EXCUTING_CONTINUE, + * executor will wait for cmd_ready() before continue to next command. + * \param str command string, e.g. "help" + */ +void cmd_exe(char *str); +/** Add alias to interpreter. + * Aliases are replaced with values before executing a command. All aliases must be started from beginning of line. + * null or empty value deletes alias. + * \code + cmd_alias_add("print", "echo"); + cmd_exe("print \"hello world!\""); // this is now same as "echo \"hello world!\"" . + * \endcode + * \param alias alias name + * \param value value for alias. Values can be any visible ASCII -characters. + */ +void cmd_alias_add(const char *alias, const char *value); +/** Add Variable to interpreter. + * Variables are replaced with values before executing a command. + * To use variables from cli, use dollar ($) -character so that interpreter knows user want to use variable in that place. + * null or empty value deletes variable. + * \code + cmd_variable_add("world", "hello world!"); + cmd_exe("echo $world"); // this is now same as echo "hello world!" . + * \endcode + * \param variable Variable name, which will be replaced in interpreter. + * \param value Value for variable. Values can contains white spaces and '"' or '"' characters. + */ +void cmd_variable_add(char *variable, char *value); + +/** find command parameter index by key. + * e.g. + * \code + int main(void){ + //..init cmd.. + //.. + cmd_exe("mycmd enable") + } + int mycmd_command(int argc, char *argv[]) { + bool found = cmd_parameter_index( argc, argv, "enable" ) > 0; + } + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key option key, which index you want to find out. + * \return index where parameter was or -1 when not found + */ +int cmd_parameter_index(int argc, char *argv[], const char *key); +/** check if command option is present. + * e.g. cmd: "mycmd -c" + * \code + * bool on = cmd_has_option( argc, argv, "p" ); + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key option key to be find + * \return true if option found otherwise false + */ +bool cmd_has_option(int argc, char *argv[], const char *key); +/** find command parameter by key. + * if exists, return true, otherwise false. + * e.g. cmd: "mycmd enable 1" + * \code + int mycmd_command(int argc, char *argv[]) { + bool value; + bool found = cmd_parameter_bool( argc, argv, "mykey", &value ); + if( found ) return CMDLINE_RETCODE_SUCCESS; + else return CMDLINE_RETCODE_FAIL; + } + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key parameter key to be find + * \param value parameter value to be fetch, if key not found value are untouched. "1" and "on" and "true" and "enable" and "allow" are True -value, all others false. + * \return true if parameter key and value found otherwise false + */ +bool cmd_parameter_bool(int argc, char *argv[], const char *key, bool *value); +/** find command parameter by key and return value (next parameter). + * if exists, return parameter pointer, otherwise null. + * e.g. cmd: "mycmd mykey myvalue" + * \code + int mycmd_command(int argc, char *argv[]) { + char *value; + bool found = cmd_parameter_val( argc, argv, "mykey", &value ); + if( found ) return CMDLINE_RETCODE_SUCCESS; + else return CMDLINE_RETCODE_FAIL; + } + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key parameter key to be find + * \param value pointer to pointer, which will point to cli input data when key and value found. if key or value not found this parameter are untouched. + * \return true if parameter key and value found otherwise false + */ +bool cmd_parameter_val(int argc, char *argv[], const char *key, char **value); +/** find command parameter by key and return value (next parameter) in integer. Only whitespaces are allowed in addition to the float to be read. + * e.g. cmd: "mycmd mykey myvalue" + * \code + int32_t value; + cmd_parameter_int( argc, argv, "key", &value ); + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the item 0 in the list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key parameter key to be found + * \param value A pointer to a variable where to write the converted number. If value cannot be converted, it is not touched. + * \return true if parameter key and an integer is found, otherwise return false + */ +bool cmd_parameter_int(int argc, char *argv[], const char *key, int32_t *value); +/** find command parameter by key and return value (next parameter) in float. Only whitespaces are allowed in addition to the float to be read. + * e.g. cmd: "mycmd mykey myvalue" + * \code + float value; + cmd_parameter_float( argc, argv, "key", &value ); + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. values begin from 1 and this means that the item 0 in the list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key parameter key to be found + * \param value A pointer to a variable where to write the converted number. If value cannot be converted, it is not touched. + * \return true if parameter key and a float found, otherwise return false + */ +bool cmd_parameter_float(int argc, char *argv[], const char *key, float *value); +/** Get last command line parameter as string. + * e.g. + * cmd: "mycmd hello world" + * cmd_parameter_last -> "world" + * cmd: "mycmd" + * cmd_parameter_last() -> NULL + * \code + cmd_parameter_last(argc, argv) + * \endcode + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \return pointer to last parameter or NULL when there is no any parameters. + */ +char *cmd_parameter_last(int argc, char *argv[]); + +/** find command parameter by key and return value (next parameter) in int64. + * e.g. cmd: "mycmd mykey myvalue" + * \code + uint32_t i; + cmd_parameter_timestamp( argc, argv, "mykey", &i ); + * \endcode + * + * Supports following formats: + * number -> direct conversion + * 11:22:33:44:55:66:77:88 -> converts to number + * seconds,tics -> converts thread type timestamp to int64 + * + * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. + * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. + * \param key parameter key to be find + * \param value parameter value to be fetch, if key not found value are untouched. + * \return true if parameter key and value found otherwise false + */ +bool cmd_parameter_timestamp(int argc, char *argv[], const char *key, int64_t *value); + +#ifdef __cplusplus +} +#endif +#endif /*_CMDLINE_H_*/ diff --git a/features/frameworks/mbed-client-cli/source/ns_cmdline.c b/features/frameworks/mbed-client-cli/source/ns_cmdline.c new file mode 100644 index 00000000000..55e4d000a12 --- /dev/null +++ b/features/frameworks/mbed-client-cli/source/ns_cmdline.c @@ -0,0 +1,1829 @@ +/* + * Copyright (c) 2016 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * 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. + */ + +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(__unix__) || defined(__unix) || defined(unix) || defined(YOTTA_CFG) +#include //malloc +#ifndef MEM_ALLOC +#define MEM_ALLOC malloc +#endif +#ifndef MEM_FREE +#define MEM_FREE free +#endif +#else +#include "nsdynmemLIB.h" +#ifndef MEM_ALLOC +#define MEM_ALLOC ns_dyn_mem_temporary_alloc +#endif +#ifndef MEM_FREE +#define MEM_FREE ns_dyn_mem_free +#endif +#endif + +// force traces for this module +//#define FEA_TRACE_SUPPORT + + +#ifdef YOTTA_CFG +#include "ns_list_internal/ns_list.h" +#include "mbed-client-cli/ns_cmdline.h" +#else +#include "ns_list.h" +#include "ns_cmdline.h" +#endif +#include "mbed-trace/mbed_trace.h" + +//#define TRACE_DEEP +//#define TRACE_PRINTF + +#ifdef TRACE_PRINTF +#undef tr_debug +#define tr_debug(...) printf( __VA_ARGS__);printf("\r\n") +#endif + +// MBED_CLIENT_CLI_TRACE_ENABLE is to enable the traces for debugging, +// default all debug traces are disabled. +#ifndef MBED_CLIENT_CLI_TRACE_ENABLE +#undef tr_error +#define tr_error(...) +#undef tr_warn +#define tr_warn(...) +#undef tr_debug +#define tr_debug(...) +#undef tr_info +#define tr_info(...) +#endif + +#ifdef TRACE_DEEP +#define tr_deep tr_debug +#else +#define tr_deep(...) +#endif + +#define TRACE_GROUP "cmdL" + +/*ASCII defines*/ +#define ESC 0x1B +#define DEL 0x7F +#define BS 0x08 +#define ETX 0x03 +#define TAB 0x09 +#define CAN 0x18 + +// Maximum length of input line +#ifdef MBED_CMDLINE_MAX_LINE_LENGTH +#define MAX_LINE MBED_CMDLINE_MAX_LINE_LENGTH +#else +#define MAX_LINE 2000 +#endif +// Maximum number of arguments in a single command +#ifdef MBED_CMDLINE_ARGUMENTS_MAX_COUNT +#define MAX_ARGUMENTS MBED_CMDLINE_ARGUMENTS_MAX_COUNT +#else +#define MAX_ARGUMENTS 30 +#endif +// Maximum number of commands saved in history +#ifdef MBED_CMDLINE_HISTORY_MAX_COUNT +#define HISTORY_MAX_COUNT MBED_CMDLINE_HISTORY_MAX_COUNT +#else +#define HISTORY_MAX_COUNT 32 +#endif + +//include manuals or not (save memory a little when not include) +#define INCLUDE_MAN + + +typedef struct cmd_history_s { + char *command_ptr; + ns_list_link_t link; +} cmd_history_t; + +typedef struct cmd_command_s { + const char *name_ptr; + const char *info_ptr; + const char *man_ptr; + cmd_run_cb *run_cb; + bool busy; + ns_list_link_t link; +} cmd_command_t; + +typedef struct cmd_alias_s { + char *name_ptr; + char *value_ptr; + ns_list_link_t link; +} cmd_alias_t; + +typedef struct cmd_variable_s { + char *name_ptr; + char *value_ptr; + ns_list_link_t link; +} cmd_variable_t; + +typedef enum operator_s { + OPERATOR_SEMI_COLON, //default + OPERATOR_AND, + OPERATOR_OR, + OPERATOR_BACKGROUND, + OPERATOR_PIPE +} operator_t; +typedef struct cmd_exe_s { + char *cmd_s; + operator_t operator; + ns_list_link_t link; +} cmd_exe_t; +typedef NS_LIST_HEAD(cmd_exe_t, link) cmd_list_t; +typedef NS_LIST_HEAD(cmd_history_t, link) history_list_t; +typedef NS_LIST_HEAD(cmd_command_t, link) command_list_t; +typedef NS_LIST_HEAD(cmd_alias_t, link) alias_list_t; +typedef NS_LIST_HEAD(cmd_variable_t, link) variable_list_t; + +typedef struct cmd_class_s { + char input[MAX_LINE]; // input data + char escape[10]; // escape data + int16_t history; // history position + int16_t cursor; // cursor position + int16_t escape_index; // escape index + history_list_t history_list; // input history + uint8_t history_max_count; // history max size + command_list_t command_list; // commands list + alias_list_t alias_list; // alias list + variable_list_t variable_list; // variables list + bool vt100_on; // control characters + bool init; // true when lists are initialized already + bool escaping; // escaping input + bool insert; // insert enabled + int tab_lookup; // originally lookup characters count + int tab_lookup_cmd_n; // index in command list + int tab_lookup_n; // + bool echo; // echo inputs + char *retcode_fmt; // retcode format + bool print_retcode; // print retcode after command is executed + cmd_ready_cb_f *ready_cb; // ready cb function + cmd_list_t cmd_buffer; + cmd_exe_t *cmd_buffer_ptr; + cmd_command_t *cmd_ptr; + int8_t tasklet_id; + int8_t network_tasklet_id; + bool idle; + + cmd_print_t *out; // print cb function + void (*ctrl_fnc)(uint8_t c); // control cb function + void (*mutex_wait_fnc)(void); // mutex wait cb function + void (*mutex_release_fnc)(void); // mutex release cb function + input_passthrough_func_t passthrough_fnc; // input passthrough cb function +} cmd_class_t; + +cmd_class_t cmd = { .init = false, .retcode_fmt = NULL, .cmd_ptr = NULL, .mutex_wait_fnc = NULL, .mutex_release_fnc = NULL, .passthrough_fnc = NULL }; + +/* Function prototypes + */ +static void cmd_init_base_commands(void); +static void cmd_replace_alias(char *input); +static void cmd_replace_variables(char *input); +static int cmd_parse_argv(char *string_ptr, char **argv); +static void cmd_execute(void); +static void cmd_line_clear(int from); +static void cmd_history_save(int16_t index); +static void cmd_history_get(uint16_t index); +static void cmd_history_clean_overflow(void); +static void cmd_history_clean(void); +static void cmd_echo(bool on); +static cmd_history_t *cmd_history_find(int16_t index); +static bool cmd_tab_lookup(void); +static const char *cmd_input_lookup(char *name, int namelength, int n); +static char *cmd_input_lookup_var(char *name, int namelength, int n); +static cmd_command_t *cmd_find(const char *name); +static cmd_command_t *cmd_find_n(char *name, int nameLength, int n); +static cmd_alias_t *alias_find(const char *alias); +static cmd_alias_t *alias_find_n(char *alias, int aliaslength, int n); +static cmd_variable_t *variable_find(char *variable); +static cmd_variable_t *variable_find_n(char *variable, int length, int n); +static void cmd_print_man(cmd_command_t *command_ptr); +static void goto_end_of_history(void); +static void goto_beginning_of_history(void); +static void cmd_set_input(const char *str, int cur); +static void cmd_set_retfmt(char *fmt); +static char *next_command(char *string_ptr, operator_t *mode); +/** Run single command through cmd intepreter + * \param string_ptr command string with parameters + * \ret command return code (CMDLINE_RETCODE_*) + */ +static int cmd_run(char *string_ptr); +static cmd_exe_t *cmd_next_ptr(int retcode); +static void cmd_split(char *string_ptr); +static void cmd_push(char *cmd_str, operator_t oper); + +/*internal shell commands + */ +int echo_command(int argc, char *argv[]); +int alias_command(int argc, char *argv[]); +int set_command(int argc, char *argv[]); +int clear_command(int argc, char *argv[]); +int help_command(int argc, char *argv[]); +int history_command(int argc, char *argv[]); + +void default_cmd_response_out(const char *fmt, va_list ap) +{ + vprintf(fmt, ap); + fflush(stdout); +} +void cmd_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + cmd_vprintf(fmt, ap); + va_end(ap); +} +void cmd_vprintf(const char *fmt, va_list ap) +{ + if (cmd.mutex_wait_fnc) { + cmd.mutex_wait_fnc(); + } + cmd.out(fmt, ap); + if (cmd.mutex_release_fnc) { + cmd.mutex_release_fnc(); + } +} +/* Function definitions + */ +void cmd_init(cmd_print_t *outf) +{ + if (!cmd.init) { + ns_list_init(&cmd.alias_list); + ns_list_init(&cmd.history_list); + ns_list_init(&cmd.command_list); + ns_list_init(&cmd.variable_list); + ns_list_init(&cmd.cmd_buffer); + cmd.init = true; + } + cmd.out = outf ? outf : default_cmd_response_out; + cmd.ctrl_fnc = NULL; + cmd.echo = true; + cmd.print_retcode = false; + cmd.escaping = false; + cmd.insert = true; + cmd.cursor = 0; + cmd.vt100_on = true; + cmd.history_max_count = HISTORY_MAX_COUNT; + cmd.tab_lookup = 0; + cmd.tab_lookup_cmd_n = 0; + cmd.tab_lookup_n = 0; + cmd.cmd_buffer_ptr = 0; + cmd.idle = true; + cmd.ready_cb = cmd_next; + cmd.passthrough_fnc = NULL; + cmd_set_retfmt("retcode: %i\r\n"); + cmd_line_clear(0); // clear line + cmd_history_save(0); // the current line is the 0 item + //cmd_free(); + cmd_init_base_commands(); + cmd_init_screen(); + return; +} + +#ifdef INCLUDE_MAN +#define MAN_ECHO "Displays messages, or turns command echoing on or off\r\n"\ + "echo \r\n"\ + "some special parameters:\r\n"\ + " On/Off echo input characters\r\n" +#define MAN_ALIAS "alias \r\n" +#define MAN_SET "set \r\n"\ + "some special parameters\r\n"\ + "--vt100 On/Off vt100 controls\r\n"\ + "--retcode On/Off retcode print after execution\r\n"\ + "--retfmt Return print format. Default: \"retcode: %i\\n\"\r\n" + +#define MAN_CLEAR "Clears the display" +#define MAN_HISTORY "Show commands history\r\n"\ + "history ()\r\n"\ + "clear Clear history\r\n" +#else +#define MAN_ECHO NULL +#define MAN_ALIAS NULL +#define MAN_SET NULL +#define MAN_CLEAR NULL +#define MAN_HISTORY NULL +#endif + +static void cmd_init_base_commands(void) +{ + cmd_add("help", help_command, "This help", NULL); + cmd_add("echo", echo_command, "Echo controlling", MAN_ECHO); + cmd_add("alias", alias_command, "Handle aliases", MAN_ALIAS); + cmd_add("set", set_command, "Handle variables", MAN_SET); + cmd_add("clear", clear_command, "Clears the display", MAN_CLEAR); + cmd_add("history", history_command, "View your command Line History", MAN_HISTORY); +} +void cmd_reset(void) +{ + cmd_free(); + cmd_init_base_commands(); +} +void cmd_free(void) +{ + ns_list_foreach_safe(cmd_command_t, cur_ptr, &cmd.command_list) { + cmd_delete(cur_ptr->name_ptr); + } + ns_list_foreach_safe(cmd_alias_t, cur_ptr, &cmd.alias_list) { + cmd_alias_add(cur_ptr->name_ptr, NULL); + } + ns_list_foreach_safe(cmd_variable_t, cur_ptr, &cmd.variable_list) { + cmd_variable_add(cur_ptr->name_ptr, NULL); + } + ns_list_foreach_safe(cmd_history_t, cur_ptr, &cmd.history_list) { + MEM_FREE(cur_ptr->command_ptr); + ns_list_remove(&cmd.history_list, cur_ptr); + MEM_FREE(cur_ptr); + } + cmd.mutex_wait_fnc = NULL; + cmd.mutex_release_fnc = NULL; +} + +void cmd_input_passthrough_func(input_passthrough_func_t passthrough_fnc) +{ + cmd.passthrough_fnc = passthrough_fnc; +} + +void cmd_exe(char *str) +{ + cmd_split(str); + if (cmd.cmd_buffer_ptr == 0) { + //execution buffer is empty + cmd.idle = false; //not really, but fake it + cmd_ready(CMDLINE_RETCODE_SUCCESS); + } else { + tr_debug("previous cmd is still in progress"); + } +} +void cmd_set_ready_cb(cmd_ready_cb_f *cb) +{ + cmd.ready_cb = cb; +} +void cmd_ready(int retcode) +{ + if( cmd.cmd_ptr && cmd.cmd_ptr->busy ) + { + //execution finished + cmd.cmd_ptr->busy = false; + } + if (!cmd.idle) { + if (cmd.cmd_buffer_ptr == NULL) { + tr_debug("goto next command"); + } else { + tr_debug("cmd '%s' executed with retcode: %i", cmd.cmd_buffer_ptr->cmd_s, retcode); + } + if (cmd.ready_cb == NULL) { + tr_warn("Missing ready_cb! use cmd_set_ready_cb()"); + } else { + cmd.ready_cb(retcode); + } + } else { + tr_warn("Someone call cmd_ready(%i) even there shouldn't be any running cmd", retcode); + if (cmd.echo) { + cmd_output(); //refresh if this happens + } + } +} +void cmd_next(int retcode) +{ + cmd.idle = true; + //figure out next command + cmd.cmd_buffer_ptr = cmd_next_ptr(retcode); + if (cmd.cmd_buffer_ptr) { + cmd.idle = false; + //yep there was some -> lets execute it + retcode = cmd_run(cmd.cmd_buffer_ptr->cmd_s); + //check if execution goes to the backend or not + if (retcode == CMDLINE_RETCODE_EXCUTING_CONTINUE ) { + if( (NULL != cmd.cmd_buffer_ptr) && cmd.cmd_buffer_ptr->operator == OPERATOR_BACKGROUND ) + { + //execution continue in background, but operator say that it's "ready" + cmd_ready(CMDLINE_RETCODE_SUCCESS); + } else { + //command execution phase continuous in background + tr_debug("Command execution continuous in background.."); + } + } else { + //execution finished -> call ready function with retcode + cmd_ready(retcode); + } + } else { + if (cmd.print_retcode) { + cmd_printf(cmd.retcode_fmt, retcode); + } + cmd_line_clear(0); + if (cmd.echo) { + cmd_output(); //ready + } + } +} +static cmd_exe_t *cmd_pop(void) +{ + cmd_exe_t *cmd_ptr = ns_list_get_first(&cmd.cmd_buffer), + *next_cmd = ns_list_get_next(&cmd.cmd_buffer, cmd_ptr); + + if (cmd.cmd_buffer_ptr == 0) { + //was first in bool-- + next_cmd = ns_list_get_first(&cmd.cmd_buffer); + } else { + MEM_FREE(cmd_ptr->cmd_s); + ns_list_remove(&cmd.cmd_buffer, cmd_ptr); + MEM_FREE(cmd_ptr); + } + return next_cmd; +} + +static cmd_exe_t *cmd_next_ptr(int retcode) +{ + cmd_exe_t *next_cmd = cmd.cmd_buffer_ptr; + if (ns_list_is_empty(&cmd.cmd_buffer)) { + return NULL; + } + if (cmd.cmd_buffer_ptr == NULL) { + return cmd_pop(); + } + retcode = retcode; + switch (cmd.cmd_buffer_ptr->operator) { + case (OPERATOR_AND): + if (retcode != CMDLINE_RETCODE_SUCCESS) { + //if fails, go to next command, which not have AND operator + while ((next_cmd->operator == OPERATOR_AND) && ((next_cmd = cmd_pop()) != 0)); + } else { + next_cmd = cmd_pop(); + } + break; + case (OPERATOR_OR): + if (retcode == CMDLINE_RETCODE_SUCCESS) { + //if fails, go to next command, which not have OR operator + while ((next_cmd->operator == OPERATOR_OR) && ((next_cmd = cmd_pop()) != 0)); + } else { + next_cmd = cmd_pop(); + } + break; + case (OPERATOR_BACKGROUND): + next_cmd = cmd_pop(); + break; + case (OPERATOR_PIPE): + /// @TODO + case (OPERATOR_SEMI_COLON): + default: + //get next command to be execute (might be null if there is no more) + next_cmd = cmd_pop(); + break; + } + + //return next command if any + return next_cmd; +} +static void cmd_split(char *string_ptr) +{ + char *ptr = string_ptr, *next; + operator_t oper = OPERATOR_SEMI_COLON; + do { + next = next_command(ptr, &oper); + cmd_push(ptr, oper); + ptr = next; + if (next && !*next) { + break; + } + } while (ptr != 0); +} + +static void cmd_push(char *cmd_str, operator_t oper) +{ + //store this command to the stack + cmd_exe_t *cmd_ptr = MEM_ALLOC(sizeof(cmd_exe_t)); + cmd_ptr->cmd_s = MEM_ALLOC(strlen(cmd_str) + 1); + strcpy(cmd_ptr->cmd_s, cmd_str); + cmd_ptr->operator = oper; + ns_list_add_to_end(&cmd.cmd_buffer, cmd_ptr); +} +void cmd_out_func(cmd_print_t *outf) +{ + cmd.out = outf; +} +void cmd_ctrl_func(void (*sohf)(uint8_t c)) +{ + cmd.ctrl_fnc = sohf; +} + +void cmd_mutex_wait_func(void (*mutex_wait_f)(void)) +{ + cmd.mutex_wait_fnc = mutex_wait_f; +} +void cmd_mutex_release_func(void (*mutex_release_f)(void)) +{ + cmd.mutex_release_fnc = mutex_release_f; +} + +void cmd_mutex_lock() +{ + if (cmd.mutex_wait_fnc) { + cmd.mutex_wait_fnc(); + } +} + +void cmd_mutex_unlock() +{ + if (cmd.mutex_release_fnc) { + cmd.mutex_release_fnc(); + } +} + +void cmd_init_screen() +{ + if (cmd.vt100_on) { + cmd_printf("\r\x1b[2J"); /* Clear screen */ + cmd_printf("\x1b[7h"); /* enable line wrap */ + } + cmd_printf("ARM Ltd\r\n"); + cmd_output(); +} +uint8_t cmd_history_size(uint8_t max) +{ + if (max > 0) { + cmd.history_max_count = max; + cmd_history_clean_overflow(); + } + return cmd.history_max_count; +} +static void cmd_echo(bool on) +{ + cmd.echo = on; +} +bool cmd_echo_state(void) +{ + return cmd.echo; +} +static cmd_command_t *cmd_find_n(char *name, int nameLength, int n) +{ + cmd_command_t *cmd_ptr = NULL; + int i = 0; + if (name != NULL && nameLength != 0) { + ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { + if (strncmp(name, cur_ptr->name_ptr, nameLength) == 0) { + if (i == n) { + cmd_ptr = cur_ptr; + break; + } + i++; + } + } + } + return cmd_ptr; +} +static const char *cmd_input_lookup(char *name, int namelength, int n) +{ + const char *str = NULL; + cmd_command_t *cmd_ptr = cmd_find_n(name, namelength, n); + if (cmd_ptr) { + str = cmd_ptr->name_ptr; + cmd.tab_lookup_n = n + 1; + } else { + n -= cmd.tab_lookup_n; + cmd_alias_t *alias = alias_find_n(name, namelength, n); + if (alias) { + str = (const char*)alias->name_ptr; + } + } + + return str; +} +static char *cmd_input_lookup_var(char *name, int namelength, int n) +{ + char *str = NULL; + cmd_variable_t *var = variable_find_n(name, namelength, n); + if (var) { + str = var->name_ptr; + } + return str; +} +static cmd_command_t *cmd_find(const char *name) +{ + cmd_command_t *cmd_ptr = NULL; + if (name == NULL || strlen(name) == 0) { + tr_error("cmd_find invalid parameters"); + return NULL; + } + + ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { + if (strcmp(name, cur_ptr->name_ptr) == 0) { + cmd_ptr = cur_ptr; + break; + } + } + return cmd_ptr; +} + +void cmd_add(const char *name, cmd_run_cb *callback, const char *info, const char *man) +{ + cmd_command_t *cmd_ptr; + + if (name == NULL || callback == NULL || strlen(name) == 0) { + tr_warn("cmd_add invalid parameters"); + return; + } + cmd_ptr = (cmd_command_t *)MEM_ALLOC(sizeof(cmd_command_t)); + cmd_ptr->name_ptr = name; + cmd_ptr->info_ptr = info; + cmd_ptr->man_ptr = man; + cmd_ptr->run_cb = callback; + cmd_ptr->busy = false; + ns_list_add_to_end(&cmd.command_list, cmd_ptr); + return; +} + +void cmd_delete(const char *name) +{ + cmd_command_t *cmd_ptr; + cmd_ptr = cmd_find(name); + if (cmd_ptr == NULL) { + return; + } + ns_list_remove(&cmd.command_list, cmd_ptr); + MEM_FREE(cmd_ptr); + return; +} + +static int cmd_parse_argv(char *string_ptr, char **argv) +{ + int argc = 0; + char *str_ptr, *end_quote_ptr = NULL; + + if (string_ptr == NULL || strlen(string_ptr) == 0) { + tr_error("Invalid parameters"); + return 0; + } + str_ptr = string_ptr; + do { + argv[argc] = str_ptr; + if (*str_ptr == '"') { + // check if end quote + end_quote_ptr = strstr(str_ptr + 1, "\""); + } + if (*str_ptr == '"' && end_quote_ptr != NULL) { + // remove quotes give as one parameter + argv[argc]++; + str_ptr = end_quote_ptr; + } else { + str_ptr = strstr(str_ptr, " "); + } + argc++; + if (str_ptr == NULL) { + break; + } + if (argc > MAX_ARGUMENTS) { + break; + } + *str_ptr++ = 0; + while (*str_ptr == ' ') { + str_ptr++; // skip spaces + } + } while (*str_ptr != 0); + return argc; +} +static void cmd_print_man(cmd_command_t *command_ptr) +{ + if (command_ptr->man_ptr) { + cmd_printf("%s\r\n", command_ptr->man_ptr); + } +} +static void cmd_set_input(const char *str, int cur) +{ + cmd_line_clear(cur); + strcpy(cmd.input + cur, str); + cmd.cursor = strlen(cmd.input); +} +/** + * If oper is not null, function set null pointers + */ +static char *next_command(char *string_ptr, operator_t *oper) +{ + char *ptr = string_ptr; + bool quote = false; + while (*ptr != 0) { + if (quote) { + if (*ptr == '"') { + quote = false; + } + } else { + //skip if previous character is '\' + if ((ptr == string_ptr) || (*(ptr - 1) != '\\')) { + switch (*ptr) { + case ('"'): { + quote = true; + break; + } + case (';'): //default operator + if (oper) { + *oper = OPERATOR_SEMI_COLON; + *ptr = 0; + } + return ptr + 1; + case ('&'): + if (ptr[1] == '&') { + if (oper) { + *oper = OPERATOR_AND; + *ptr = 0; + } + return ptr + 2; + } else { + if (oper) { + *oper = OPERATOR_BACKGROUND; + *ptr = 0; + } + return ptr + 1; + } + case ('|'): + if (ptr[1] == '|') { + tr_warn("or operator not supported"); + if (oper) { + *oper = OPERATOR_OR; + *ptr = 0; + } + return ptr + 2; + } else { + tr_warn("pipe operator not supported"); + if (oper) { + *oper = OPERATOR_PIPE; + *ptr = 0; + } + return ptr + 1; + } + default: + break; + } + } + } + ptr++; + } + return 0; +} +static int cmd_run(char *string_ptr) +{ + char *argv[MAX_ARGUMENTS]; + int argc, ret; + + tr_info("Executing cmd: '%s'", string_ptr); + char *command_str = MEM_ALLOC(MAX_LINE); + while (isspace((unsigned char) *string_ptr) && + *string_ptr != '\n' && + *string_ptr != 0) { + string_ptr++; //skip white spaces + } + strcpy(command_str, string_ptr); + tr_deep("cmd_run('%s') ", command_str); + cmd_replace_alias(command_str); + cmd_replace_variables(command_str); + tr_debug("Parsed cmd: '%s'", command_str); + + argc = cmd_parse_argv(command_str, argv); + + cmd.cmd_ptr = cmd_find(argv[0]); + + if (cmd.cmd_ptr == NULL) { + cmd_printf("Command '%s' not found.\r\n", argv[0]); + MEM_FREE(command_str); + return CMDLINE_RETCODE_COMMAND_NOT_FOUND; + } + if (cmd.cmd_ptr->run_cb == NULL) { + tr_error("Command callback missing"); + MEM_FREE(command_str); + return CMDLINE_RETCODE_COMMAND_CB_MISSING; + } + + if (argc == 2 && + (cmd_has_option(argc, argv, "h") || cmd_parameter_index(argc, argv, "--help") > 0)) { + MEM_FREE(command_str); + cmd_print_man(cmd.cmd_ptr); + return CMDLINE_RETCODE_SUCCESS; + } + + if( cmd.cmd_ptr->busy ) { + MEM_FREE(command_str); + return CMDLINE_RETCODE_COMMAND_BUSY; + } + + // Run the actual callback + cmd.cmd_ptr->busy = true; + ret = cmd.cmd_ptr->run_cb(argc, argv); +#ifndef TEST + cmd_alias_add("_", command_str); // last executed command +#endif + MEM_FREE(command_str); + switch (ret) { + case (CMDLINE_RETCODE_COMMAND_NOT_IMPLEMENTED): + tr_warn("Command not implemented"); + break; + case (CMDLINE_RETCODE_INVALID_PARAMETERS): + tr_warn("Command parameter was incorrect"); + cmd_printf("Invalid parameters!\r\n"); + cmd_print_man(cmd.cmd_ptr); + break; + default: + break; + } + return ret; +} + +void cmd_escape_start(void) +{ + cmd.escaping = true; + memset(cmd.escape, 0, sizeof(cmd.escape)); + cmd.escape_index = 0; +} +char *strrevchr(const char *begin, const char *end, char ch) +{ + char *ptr = (char *)end; + while (begin <= ptr) { + if (*ptr == ch) { + return ptr; + } + if (*ptr == 0) { + return ptr; + } + ptr--; + } + return 0; +} +void cmd_escape_read(int16_t u_data) +{ + int16_t old_entry; + + tr_debug("cmd_escape_read: %02x '%c'", u_data, (isprint(u_data) ? u_data : '?')); + + if (u_data == '[') { + /*first character for longer escape sequence ends in character*/ + cmd.escape[cmd.escape_index++] = '['; + return; + } + if (u_data == 'D') { + /* Arrow left*/ + if ((cmd.escape_index == 1 && cmd.escape[0] == 'O') || + (cmd.escape_index == 4 && strncmp(cmd.escape + 1, "1;5", 3) == 0)) { + + char *ptr = cmd.input + cmd.cursor; + if (*ptr == ' ' || *ptr == 0) { + ptr--; + } + ptr = strrevchr(cmd.input, ptr, ' '); //@todo not working way that we want + if (ptr) { + cmd.cursor = ptr - cmd.input; + } else { + cmd.cursor = 0; + } + } else { + cmd.cursor --; + } + if (cmd.cursor < 0) { + cmd.cursor = 0; + } + } else if (u_data == 'C') { + /* Arrow Right*/ + if ((cmd.escape_index == 1 && cmd.escape[0] == 'O') || + (cmd.escape_index == 4 && strncmp(cmd.escape + 1, "1;5", 3) == 0)) { + char *ptr = cmd.input + cmd.cursor; + if (*ptr == ' ') { + ptr++; + } + ptr = strchr(ptr, ' '); + if (ptr) { + cmd.cursor = ptr - cmd.input; + } else { + cmd.cursor = strlen(cmd.input); + } + } else { + cmd.cursor ++; + } + if ((int)cmd.cursor > (int)strlen(cmd.input)) { + cmd.cursor = strlen(cmd.input); + } + } else if (u_data == 'A') { + /* Arrow Up*/ + old_entry = cmd.history++; + if (NULL == cmd_history_find(cmd.history)) { + cmd.history = old_entry; + } + if (old_entry != cmd.history) { + cmd_history_save(old_entry); + cmd_history_get(cmd.history); + } + } else if (u_data == 'B') { + /* Arrow Down*/ + old_entry = cmd.history--; + if (cmd.history < 0) { + cmd.history = 0; + } + + if (old_entry != cmd.history) { + cmd_history_save(old_entry); + cmd_history_get(cmd.history); + } + } else if (u_data == 'Z') { + // Shift+TAB + if (cmd.tab_lookup > 0) { + cmd.cursor = cmd.tab_lookup; + cmd.input[cmd.tab_lookup] = 0; + if (cmd.tab_lookup_cmd_n > 0) { + cmd.tab_lookup_cmd_n--; + } + } + cmd_tab_lookup(); + } else if (u_data == 'H') { + // Xterm support + cmd.cursor = 0; + } else if (u_data == 'F') { + // Xterm support + cmd.cursor = strlen(cmd.input); + } else if (isdigit((int)cmd.escape[cmd.escape_index - 1]) && u_data == '~') { + switch (cmd.escape[cmd.escape_index - 1]) { + case ('1'): //beginning-of-line # Home key + cmd.cursor = 0; + break; + case ('2'): //quoted-insert # Insert key + cmd.insert = !cmd.insert; + break; + case ('3'): //delete-char # Delete key + if ((int)strlen(cmd.input) > (int)cmd.cursor) { + memmove(&cmd.input[cmd.cursor], &cmd.input[cmd.cursor + 1], strlen(&cmd.input[cmd.cursor + 1]) + 1); + } + break; + case ('4'): //end-of-line # End key + cmd.cursor = strlen(cmd.input); + break; + case ('5'): //beginning-of-history # PageUp key + goto_end_of_history(); + break; + case ('6'): //end-of-history # PageDown key + goto_beginning_of_history(); + break; + default: + break; + } + } else if (isprint(u_data)) { //IS_NUMBER || IS_CONTROL + cmd.escape[cmd.escape_index++] = u_data; + return; + } + cmd_output(); + cmd.escaping = false; + return; +} +static void goto_end_of_history(void) +{ + // handle new input if any and verify that + // it is not already in beginning of history or current position + bool allowStore = strlen(cmd.input) != 0; //avoid store empty lines to history + cmd_history_t *entry_ptr; + if (cmd.history > 0 && allowStore) { + entry_ptr = cmd_history_find(cmd.history); + if (entry_ptr) { + if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { + // current history contains contains same text as input + allowStore = false; + } + } + } else if (allowStore && (entry_ptr = cmd_history_find(0)) != NULL) { + if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { + //beginning of history was same text as input + allowStore = false; + } + } + if (allowStore) { + cmd_history_save(0); // new is saved to place 0 + cmd_history_save(-1); // new is created to the current one + } + + cmd_history_t *cmd_ptr = ns_list_get_last(&cmd.history_list); + cmd_set_input(cmd_ptr->command_ptr, 0); + cmd.history = ns_list_count(&cmd.history_list) - 1; +} +static void goto_beginning_of_history(void) +{ + cmd_history_t *cmd_ptr = ns_list_get_first(&cmd.history_list); + cmd_set_input(cmd_ptr->command_ptr, 0); + cmd.history = 0; +} +static void cmd_reset_tab(void) +{ + cmd.tab_lookup = 0; + cmd.tab_lookup_cmd_n = 0; + cmd.tab_lookup_n = 0; +} +void cmd_char_input(int16_t u_data) +{ + /*Handle passthrough*/ + if (cmd.passthrough_fnc != NULL) { + cmd.passthrough_fnc(u_data); + return; + } + + /*handle ecape command*/ + if (cmd.escaping == true) { + cmd_escape_read(u_data); + return; + } + tr_debug("input char: %02x '%c', cursor: %i, input: \"%s\"", u_data, (isprint(u_data) ? u_data : ' '), cmd.cursor, cmd.input); + + /*Normal character input*/ + if (u_data == '\r' || u_data == '\n') { + cmd_reset_tab(); + if (strlen(cmd.input) == 0) { + if (cmd.echo) { + cmd_printf("\r\n"); + cmd_output(); + } + } else { + if (cmd.echo) { + cmd_printf("\r\n"); + } + cmd_execute(); + } + } else if (u_data == ESC) { + cmd_escape_start(); + } else if (u_data == BS || u_data == DEL) { + cmd_reset_tab(); + cmd.cursor--; + if (cmd.cursor < 0) { + cmd.cursor = 0; + return; + } + memmove(&cmd.input[cmd.cursor], &cmd.input[cmd.cursor + 1], strlen(&cmd.input[cmd.cursor + 1]) + 1); + if (cmd.echo) { + cmd_output(); + } + } else if (u_data == ETX || u_data == CAN) { + //ctrl+c (End of text) or ctrl+x (cancel) + cmd_reset_tab(); + cmd_line_clear(0); + if (!cmd.idle) { + cmd_ready(CMDLINE_RETCODE_FAIL); + } + if (cmd.echo) { + cmd_output(); + } + } else if (u_data == TAB) { + bool inc = false; + if (cmd.tab_lookup > 0) { + cmd.cursor = cmd.tab_lookup; + cmd.input[cmd.tab_lookup] = 0; + cmd.tab_lookup_cmd_n++; + inc = true; + } else { + cmd.tab_lookup = strlen(cmd.input); + } + + if (!cmd_tab_lookup()) { + if (inc) { + cmd.tab_lookup_cmd_n--; + } + memset(cmd.input + cmd.tab_lookup, 0, MAX_LINE - cmd.tab_lookup); + } + if (cmd.echo) { + cmd_output(); + } + + } else if (iscntrl(u_data)) { + if (cmd.ctrl_fnc) { + cmd.ctrl_fnc(u_data); + } + } else { + cmd_reset_tab(); + tr_deep("cursor: %d, inputlen: %d, u_data: %c\r\n", cmd.cursor, strlen(cmd.input), u_data); + if ((strlen(cmd.input) >= MAX_LINE - 1) || (cmd.cursor >= MAX_LINE - 1)) { + tr_warn("input buffer full"); + if (cmd.echo) { + cmd_output(); + } + return; + } + if (cmd.insert) { + memmove(&cmd.input[cmd.cursor + 1], &cmd.input[cmd.cursor], strlen(&cmd.input[cmd.cursor]) + 1); + } + cmd.input[cmd.cursor++] = u_data; + if (cmd.echo) { + cmd_output(); + } + } +} +static int check_variable_keylookup_size(char **key, int *keysize) +{ + if (cmd.cursor > 0 && cmd.tab_lookup > 0) { + //printf("tab_lookup: %i\r\n", cmd.tab_lookup); + char *ptr = cmd.input + cmd.tab_lookup; + do { + //printf("varkey lookup: %c\r\n", *ptr); + if (*ptr == ' ') { + return 0; + } + if (*ptr == '$') { + int varlen = cmd.tab_lookup - (ptr - cmd.input) - 1; + *key = ptr; + *keysize = varlen; + //printf("varkey size: %i\r\n", varlen); + return (ptr - cmd.input); + } + ptr--; + } while (ptr > cmd.input); + } + return 0; +} +bool cmd_tab_lookup(void) +{ + int len = strlen(cmd.input); + if (len == 0) { + return false; + } + + char *variable_keypart; + int lookupSize; + int varpos = check_variable_keylookup_size(&variable_keypart, &lookupSize); + + const char *str = NULL; + if (varpos) { + str = cmd_input_lookup_var(variable_keypart + 1, lookupSize, cmd.tab_lookup_cmd_n); + if (str) { + cmd_set_input(str, varpos + 1); + return true; + } + } else { + str = cmd_input_lookup(cmd.input, len, cmd.tab_lookup_cmd_n); + if (str != NULL) { + cmd_set_input(str, 0); + return true; + } + } + + return false; +} +void cmd_output(void) +{ + if (cmd.vt100_on && cmd.idle) { + cmd_printf("\r\x1b[2K/>%s \x1b[%dD", cmd.input, (int)strlen(cmd.input) - cmd.cursor + 1); + } +} +void cmd_echo_off(void) +{ + cmd.echo = false; +} +void cmd_echo_on(void) +{ + cmd.echo = true; +} +// alias +#ifndef TEST +static +#endif +int replace_alias(char *str, const char *old_str, const char *new_str) +{ + int old_len = strlen(old_str), + new_len = strlen(new_str); + if ((strncmp(str, old_str, old_len) == 0) && + ((str[ old_len ] == ' ') || (str[ old_len ] == 0) || + (str[ old_len ] == ';') || (str[ old_len ] == '&'))) { + memmove(str + new_len, str + old_len, strlen(str + old_len) + 1); + memcpy(str, new_str, new_len); + return new_len - old_len; + } + return 0; +} +static void cmd_replace_alias(char *input) +{ + ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { + replace_alias(input, cur_ptr->name_ptr, cur_ptr->value_ptr); + } +} +//variable +#ifndef TEST +static +#endif +void replace_variable(char *str, const char *old_str, const char *new_str) +{ + char *ptr = str; + int old_len = strlen(old_str), + new_len = strlen(new_str); + if (old_len > 0) { + while ((ptr = strstr(ptr, old_str)) != 0) { + if (ptr > str && *(ptr - 1) == '$') { + memmove(ptr + new_len - 1, ptr + old_len, strlen(ptr + old_len) + 1); + memcpy(ptr - 1, new_str, new_len); + } + ptr++; + } + } +} +void cmd_replace_variables(char *input) +{ + ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { + replace_variable(input, cur_ptr->name_ptr, cur_ptr->value_ptr); + } +} +//history +static void cmd_history_item_delete(cmd_history_t *entry_ptr) +{ + ns_list_remove(&cmd.history_list, entry_ptr); + MEM_FREE(entry_ptr->command_ptr); + MEM_FREE(entry_ptr); +} + +static cmd_history_t *cmd_history_find(int16_t index) +{ + cmd_history_t *entry_ptr = NULL; + int16_t count = 0; + ns_list_foreach(cmd_history_t, cur_ptr, &cmd.history_list) { + if (count == index) { + entry_ptr = cur_ptr; + break; + } + count++; + } + return entry_ptr; +} + +static void cmd_history_clean_overflow(void) +{ + while (ns_list_count(&cmd.history_list) > cmd.history_max_count) { + cmd_history_t *cmd_ptr = ns_list_get_last(&cmd.history_list); + tr_debug("removing older history (%s)", cmd_ptr->command_ptr); + cmd_history_item_delete(cmd_ptr); + } +} +static void cmd_history_clean(void) +{ + while (ns_list_count(&cmd.history_list) > 0) { + tr_debug("removing older history"); + cmd_history_item_delete(ns_list_get_last(&cmd.history_list)); + } +} +static void cmd_history_save(int16_t index) +{ + /*if entry true save it to first item which is the one currently edited*/ + cmd_history_t *entry_ptr; + int16_t len; + + len = strlen(cmd.input); + + tr_debug("saving history item %d", index); + entry_ptr = cmd_history_find(index); + + if (entry_ptr == NULL) { + /*new entry*/ + entry_ptr = (cmd_history_t *)MEM_ALLOC(sizeof(cmd_history_t)); + entry_ptr->command_ptr = NULL; + ns_list_add_to_start(&cmd.history_list, entry_ptr); + } + + if (entry_ptr->command_ptr != NULL) { + MEM_FREE(entry_ptr->command_ptr); + } + entry_ptr->command_ptr = (char *)MEM_ALLOC(len + 1); + strcpy(entry_ptr->command_ptr, cmd.input); + + cmd_history_clean_overflow(); +} +static void cmd_history_get(uint16_t index) +{ + cmd_history_t *entry_ptr; + + tr_debug("getting history item %d", index); + + entry_ptr = cmd_history_find(index); + + if (entry_ptr != NULL) { + memset(cmd.input, 0, MAX_LINE); + cmd_set_input(entry_ptr->command_ptr, 0); + } +} + +static void cmd_line_clear(int from) +{ + memset(cmd.input + from, 0, MAX_LINE - from); + cmd.cursor = from; +} + +static void cmd_execute(void) +{ + if (strlen(cmd.input) != 0) { + bool noduplicate = true; + cmd_history_t *entry_ptr = cmd_history_find(0); + if (entry_ptr) { + if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { + noduplicate = false; + } + } + if (noduplicate) { + cmd_history_save(0); // new is saved to place 0 + cmd_history_save(-1); // new is created to the current one + } + } + cmd.history = 0; + + tr_deep("cmd_execute('%s') ", cmd.input); + cmd_exe(cmd.input); + cmd_line_clear(0); +} + + +static cmd_alias_t *alias_find(const char *alias) +{ + cmd_alias_t *alias_ptr = NULL; + if (alias == NULL || strlen(alias) == 0) { + tr_error("alias_find invalid parameters"); + return NULL; + } + + ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { + if (strcmp(alias, cur_ptr->name_ptr) == 0) { + alias_ptr = cur_ptr; + break; + } + } + return alias_ptr; +} + +static cmd_alias_t *alias_find_n(char *alias, int aliaslength, int n) +{ + cmd_alias_t *alias_ptr = NULL; + int i = 0; + if (alias == NULL || strlen(alias) == 0) { + tr_error("alias_find invalid parameters"); + return NULL; + } + + ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { + if (strncmp(alias, cur_ptr->name_ptr, aliaslength) == 0) { + if (i == n) { + alias_ptr = cur_ptr; + break; + } + i++; + } + } + return alias_ptr; +} +static cmd_variable_t *variable_find(char *variable) +{ + cmd_variable_t *variable_ptr = NULL; + if (variable == NULL || strlen(variable) == 0) { + tr_error("variable_find invalid parameters"); + return NULL; + } + + ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { + if (strcmp(variable, cur_ptr->name_ptr) == 0) { + variable_ptr = cur_ptr; + break; + } + } + return variable_ptr; +} +static cmd_variable_t *variable_find_n(char *variable, int length, int n) +{ + cmd_variable_t *variable_ptr = NULL; + if (variable == NULL || strlen(variable) == 0) { + tr_error("variable_find invalid parameters"); + return NULL; + } + int i = 0; + ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { + if (strncmp(variable, cur_ptr->name_ptr, length) == 0) { + if (i == n) { + variable_ptr = cur_ptr; + break; + } + i++; + } + } + return variable_ptr; +} +static void cmd_alias_print_all(void) +{ + ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { + if (cur_ptr->name_ptr != NULL) { + cmd_printf("%-18s'%s'\r\n", cur_ptr->name_ptr, cur_ptr->value_ptr ? cur_ptr->value_ptr : ""); + } + } + return; +} +static void cmd_variable_print_all(void) +{ + ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { + if (cur_ptr->name_ptr != NULL) { + cmd_printf("%-18s'%s'\r\n", cur_ptr->name_ptr, cur_ptr->value_ptr ? cur_ptr->value_ptr : ""); + } + } + return; +} + +void cmd_alias_add(const char *alias, const char *value) +{ + cmd_alias_t *alias_ptr; + if (alias == NULL || strlen(alias) == 0) { + tr_warn("cmd_alias_add invalid parameters"); + return; + } + alias_ptr = alias_find(alias); + if (alias_ptr == NULL) { + if (value == NULL) { + return; // no need to add new null one + } + if (strlen(value) == 0) { + return; // no need to add new empty one + } + alias_ptr = (cmd_alias_t *)MEM_ALLOC(sizeof(cmd_alias_t)); + ns_list_add_to_end(&cmd.alias_list, alias_ptr); + alias_ptr->name_ptr = (char *)MEM_ALLOC(strlen(alias) + 1); + strcpy(alias_ptr->name_ptr, alias); + alias_ptr->value_ptr = NULL; + } + if (value == NULL || strlen(value) == 0) { + // delete this one + ns_list_remove(&cmd.alias_list, alias_ptr); + MEM_FREE(alias_ptr->name_ptr); + MEM_FREE(alias_ptr->value_ptr); + MEM_FREE(alias_ptr); + } else { + // add new or modify + if (alias_ptr->value_ptr != NULL) { + MEM_FREE(alias_ptr->value_ptr); + } + alias_ptr->value_ptr = (char *)MEM_ALLOC(strlen(value) + 1); + strcpy(alias_ptr->value_ptr, value); + } + return; +} +void cmd_variable_add(char *variable, char *value) +{ + cmd_variable_t *variable_ptr; + + if (variable == NULL || strlen(variable) == 0) { + tr_warn("cmd_variable_add invalid parameters"); + return; + } + variable_ptr = variable_find(variable); + if (variable_ptr == NULL) { + if (value == NULL) { + return; // adding null variable + } + if (strlen(value) == 0) { + return; // no need to add new empty one + } + variable_ptr = (cmd_variable_t *)MEM_ALLOC(sizeof(cmd_variable_t)); + ns_list_add_to_end(&cmd.variable_list, variable_ptr); + variable_ptr->name_ptr = (char *)MEM_ALLOC(strlen(variable) + 1); + strcpy(variable_ptr->name_ptr, variable); + variable_ptr->value_ptr = NULL; + } + if (value == NULL || strlen(value) == 0) { + // delete this one + ns_list_remove(&cmd.variable_list, variable_ptr); + MEM_FREE(variable_ptr->name_ptr); + MEM_FREE(variable_ptr->value_ptr); + MEM_FREE(variable_ptr); + } else { + // add new or modify + if (variable_ptr->value_ptr != NULL) { + MEM_FREE(variable_ptr->value_ptr); + } + variable_ptr->value_ptr = (char *)MEM_ALLOC(strlen(value) + 1); + strcpy(variable_ptr->value_ptr, value); + } + return; +} + +static bool is_cmdline_commands(char *command) +{ + if ((strncmp(command, "alias", 5) == 0) || + (strcmp(command, "echo") == 0) || + (strcmp(command, "set") == 0) || + (strcmp(command, "clear") == 0) || + (strcmp(command, "help") == 0)) { + return true; + } + return false; +} +static void cmd_set_retfmt(char *fmt) +{ + if (cmd.retcode_fmt) { + MEM_FREE(cmd.retcode_fmt); + } + cmd.retcode_fmt = MEM_ALLOC(strlen(fmt) + 1); + strcpy(cmd.retcode_fmt, fmt); +} +/*Basic commands for cmd line + * alias + * echo + * set + * clear + * help + */ +int alias_command(int argc, char *argv[]) +{ + if (argc == 1) { + // print all alias + cmd_printf("alias:\r\n"); + cmd_alias_print_all(); + } else if (argc == 2) { + // print alias + if (is_cmdline_commands(argv[1])) { + cmd_printf("Cannot overwrite default commands with alias\r\n"); + return -1; + } + tr_debug("Deleting alias %s", argv[1]); + cmd_alias_add(argv[1], NULL); + } else { + // set alias + tr_debug("Setting alias %s = %s", argv[1], argv[2]); + cmd_alias_add(argv[1], argv[2]); + } + return 0; +} +int set_command(int argc, char *argv[]) +{ + if (argc == 1) { + // print all alias + cmd_printf("variables:\r\n"); + cmd_variable_print_all(); + } else if (argc == 2) { + // print alias + tr_debug("Deleting variable %s", argv[1]); + cmd_variable_add(argv[1], NULL); + } else { + // set alias + tr_debug("Setting variable %s = %s", argv[1], argv[2]); + //handle special cases: vt100 on|off + bool state; + if (cmd_parameter_bool(argc, argv, "--vt100", &state)) { + cmd.vt100_on = state; + return 0; + } + if (cmd_parameter_bool(argc, argv, "--retcode", &state)) { + cmd.print_retcode = state; + return 0; + } + char *str; + if (cmd_parameter_val(argc, argv, "--retfmt", &str)) { + cmd_set_retfmt(str); + return 0; + } + cmd_variable_add(argv[1], argv[2]); + } + return 0; +} +int echo_command(int argc, char *argv[]) +{ + bool printEcho = false; + if (argc == 1) { + printEcho = true; + } else if (argc == 2) { + if (strcmp(argv[1], "off") == 0) { + cmd_echo(false); + printEcho = true; + } else if (strcmp(argv[1], "on") == 0) { + cmd_echo(true); + printEcho = true; + } + } + if( printEcho ) { + cmd_printf("ECHO is %s\r\n", cmd.echo ? "on" : "off"); + } else { + for (int n = 1; n < argc; n++) { + tr_deep("ECHO: %s\r\n", argv[n]); + cmd_printf("%s ", argv[n]); + } + cmd_printf("\r\n"); + } + return 0; +} + +int clear_command(int argc, char *argv[]) +{ + (void)argc; + (void )argv; + + cmd_echo(true); + cmd_init_screen(); + return 0; +} +int help_command(int argc, char *argv[]) +{ + cmd_printf("Commands:\r\n"); + if (argc == 1) { + ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { + cmd_printf("%-16s%s\r\n", cur_ptr->name_ptr, (cur_ptr->info_ptr ? cur_ptr->info_ptr : "")); + } + } else if (argc == 2) { + cmd_command_t *cmd_ptr = cmd_find(argv[1]); + if (cmd_ptr) { + cmd_printf("Command: %s\r\n", cmd_ptr->name_ptr); + if (cmd_ptr->man_ptr) { + cmd_printf("%s\r\n", cmd_ptr->man_ptr); + } else if (cmd_ptr->info_ptr) { + cmd_printf("%s\r\n", cmd_ptr->info_ptr); + } + } else { + cmd_printf("Command '%s' not found", argv[1]); + } + } + return 0; +} +int history_command(int argc, char *argv[]) +{ + if (argc == 1) { + cmd_printf("History [%i/%i]:\r\n", (int)ns_list_count(&cmd.history_list), cmd.history_max_count); + int i = 0; + ns_list_foreach_reverse(cmd_history_t, cur_ptr, &cmd.history_list) { + cmd_printf("[%i]: %s\r\n", i++, cur_ptr->command_ptr); + } + } else if (argc == 2) { + if (strcmp(argv[1], "clear") == 0) { + cmd_history_clean(); + } else { + cmd_history_size(strtoul(argv[1], 0, 10)); + } + } + return 0; +} + +/** Parameter helping functions + */ +int cmd_parameter_index(int argc, char *argv[], const char *key) +{ + int i = 0; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], key) == 0) { + return i; + } + } + return -1; +} +bool cmd_has_option(int argc, char *argv[], const char *key) +{ + int i = 0; + for (i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] != '-') { + if (strstr(argv[i], key) != 0) { + return true; + } + } + } + return false; +} +bool cmd_parameter_bool(int argc, char *argv[], const char *key, bool *value) +{ + int i = cmd_parameter_index(argc, argv, key); + if (i > 0) { + if (argc > (i + 1)) { + if (strcmp(argv[i + 1], "on") == 0 || + strcmp(argv[i + 1], "1") == 0 || + strcmp(argv[i + 1], "true") == 0 || + strcmp(argv[i + 1], "enable") == 0 || + strcmp(argv[i + 1], "allow") == 0) { + *value = true; + } else { + *value = false; + } + return true; + } + } + return false; +} +bool cmd_parameter_val(int argc, char *argv[], const char *key, char **value) +{ + int i = cmd_parameter_index(argc, argv, key); + if (i > 0) { + if (argc > (i + 1)) { + *value = argv[i + 1]; + return true; + } + } + return false; +} +bool cmd_parameter_int(int argc, char *argv[], const char *key, int32_t *value) +{ + int i = cmd_parameter_index(argc, argv, key); + char* tailptr; + if (i > 0) { + if (argc > (i + 1)) { + *value = strtol(argv[i + 1], &tailptr, 10); + if (0 == *tailptr) { + return true; + } + if (!isspace((unsigned char) *tailptr)) { + return false; + } else { + return true; + } + } + } + return false; +} +bool cmd_parameter_float(int argc, char *argv[], const char *key, float *value) +{ + int i = cmd_parameter_index(argc, argv, key); + char* tailptr; + if (i > 0) { + if (argc > (i + 1)) { + *value = strtof(argv[i + 1], &tailptr); + if (0 == *tailptr) { + return true; //Should be correct read always + } + if (!isspace((unsigned char) *tailptr)) { + return false; //Garbage in tailptr + } else { + return true; //Spaces are fine after float + } + } + } + return false; +} +// convert hex string (eg. "76 ab ff") to binary array +static int string_to_bytes(const char *str, uint8_t *buf, int bytes) +{ + int len = strlen(str); + if( len <= (3*bytes - 1)) { + int i; + for(i=0;i 0) { + if (argc > (i + 1)) { + if (strchr(argv[i + 1],',') != 0) { + // Format seconds,tics + const char splitValue[] = ", "; + char *token; + token = strtok(argv[i + 1], splitValue); + if (token) { + *value = strtoul(token, 0, 10) << 16; + } + token = strtok(NULL, splitValue); + if (token) { + *value |= (0xffff & strtoul(token, 0, 10)); + } + } else if (strchr(argv[i + 1],':') != 0 ) { + // Format 00:00:00:00:00:00:00:00 + uint8_t buf[8]; + if (string_to_bytes(argv[i + 1], buf, 8) == 0) { + *value = read_64_bit(buf); + } else { + cmd_printf("timestamp should be 8 bytes long\r\n"); + } + } else { + // Format uint64 + *value = strtol(argv[i + 1], 0, 10); + } + return true; + } + } + return false; +} +char *cmd_parameter_last(int argc, char *argv[]) +{ + if (argc > 1) { + return argv[ argc - 1 ]; + } + return NULL; +} diff --git a/features/frameworks/mbed-trace/mbed_lib.json b/features/frameworks/mbed-trace/mbed_lib.json index f51343717f4..9a2b49d7d57 100644 --- a/features/frameworks/mbed-trace/mbed_lib.json +++ b/features/frameworks/mbed-trace/mbed_lib.json @@ -8,7 +8,15 @@ "fea-ipv6": { "help": "Used to globally disable ipv6 tracing features.", "value": null + }, + "allocator": { + "value": "malloc", + "macro_name": "MEM_ALLOC" + }, + "deallocator": { + "value": "free", + "macro_name": "MEM_FREE" } - } + } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 48ee33d6ae6..3a14d41d20f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ IntelHex>=1.3 junit-xml pyYAML requests -mbed-ls>=0.2.13 +mbed-ls>=1.4.2,==1.* mbed-host-tests>=1.1.2 mbed-greentea>=0.2.24 beautifulsoup4>=4 @@ -17,3 +17,4 @@ future>=0.16.0 six>=1.11.0 git+https://github.com/armmbed/manifest-tool.git@v1.4.5 mbed-cloud-sdk==2.0.0 +icetea>=1.0.1,<2 diff --git a/tools/build_api.py b/tools/build_api.py index 601184944b3..c3140b455ea 100644 --- a/tools/build_api.py +++ b/tools/build_api.py @@ -1348,11 +1348,13 @@ def merge_build_data(filename, toolchain_report, app_type): for project in tc.values(): for build in project: try: + build[0]['bin_fullpath'] = build[0]['bin'] + build[0]['elf_fullpath'] = build[0]['elf'] build[0]['elf'] = relpath(build[0]['elf'], path_to_file) build[0]['bin'] = relpath(build[0]['bin'], path_to_file) except KeyError: pass if 'type' not in build[0]: build[0]['type'] = app_type - build_data['builds'].append(build[0]) - dump(build_data, open(filename, "w"), indent=4, separators=(',', ': ')) + build_data['builds'].insert(0, build[0]) + dump(build_data, open(filename, "wb"), indent=4, separators=(',', ': ')) diff --git a/tools/resources/__init__.py b/tools/resources/__init__.py index d19cabde5d1..f49af77259d 100644 --- a/tools/resources/__init__.py +++ b/tools/resources/__init__.py @@ -69,6 +69,7 @@ # Tests, here for simplicity 'TESTS', + 'TEST_APPS', ]) LEGACY_TOOLCHAIN_NAMES = { 'ARM_STD':'ARM', diff --git a/tools/run_icetea.py b/tools/run_icetea.py new file mode 100644 index 00000000000..35a7f7066af --- /dev/null +++ b/tools/run_icetea.py @@ -0,0 +1,332 @@ +#! /usr/bin/env python2 +""" +Copyright 2018 ARM Limited +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. +""" + +from __future__ import print_function, division, absolute_import +import sys +import os +import re +from os.path import abspath, join, dirname, relpath, sep +import json +import traceback +from fnmatch import translate +from argparse import ArgumentParser + +ROOT = abspath(join(dirname(__file__), '..')) +sys.path.insert(0, ROOT) + +from tools.config import ConfigException +from tools.utils import cmd, run_cmd + +plugins_path = abspath(join(ROOT, 'TEST_APPS', 'icetea_plugins', 'plugins_to_load.py')) + + +def find_build_from_build_data(build_data, id, target, toolchain): + + if 'builds' not in build_data: + raise Exception("build data is in wrong format, does not include builds object") + + for build in build_data['builds']: + if 'id' in build.keys() \ + and id.upper() in build['id'].upper() \ + and 'target_name' in build.keys() \ + and target.upper() == build['target_name'].upper() \ + and 'toolchain_name' in build.keys() \ + and toolchain.upper() == build['toolchain_name'].upper() \ + and 'result' in build.keys() \ + and "OK" == build['result']: + return build + return None + + +def create_test_suite(target, tool, icetea_json_output, build_data, tests_by_name): + """ + Create test suite content + :param target: + :param tool: + :param icetea_json_output: + :param build_data: + :return: + """ + test_suite = dict() + test_suite['testcases'] = list() + + for test in icetea_json_output: + skip = False + + for dut in test['requirements']['duts'].values(): + # Set binary path based on application name + if 'application' in dut.keys() and 'name' in dut['application'].keys(): + build = find_build_from_build_data( + build_data=build_data, + id=dut['application']['name'], + target=target, + toolchain=tool) + if build: + try: + dut['application']['bin'] = build['bin_fullpath'] + except KeyError: + raise Exception('Full path is missing from build: {}'.format(build)) + else: + skip = True + + if not tests_by_name or is_test_in_test_by_name(test['name'], tests_by_name): + test_case = { + 'name': test['name'], + 'config': { + 'requirements': test['requirements'] + } + } + + # Skip test if not binary path + if skip: + test_case['config']['execution'] = { + 'skip': { + 'value': True, + 'reason': "Test requiring application binary not build" + } + } + + test_suite['testcases'].append(test_case) + + return test_suite + + +def get_applications(test): + ret = list() + for dut in test['requirements']['duts'].values(): + if 'application' in dut.keys() and 'name' in dut['application'].keys(): + ret.append(dut['application']['name']) + return ret + + +def filter_test_by_build_data(icetea_json_output, build_data, target, toolchain): + if not build_data: + return icetea_json_output + + ret = list() + for test in icetea_json_output: + for dut in test['requirements']['duts'].values(): + if 'application' in dut.keys() and 'name' in dut['application'].keys(): + id = dut['application']['name'] + if find_build_from_build_data(build_data, id, target, toolchain): + # Test requiring build found + ret.append(test) + return ret + + +def filter_test_by_name(icetea_json_output, test_by_name): + if not test_by_name: + return icetea_json_output + ret = list() + for test_temp in icetea_json_output: + if is_test_in_test_by_name(test_temp['name'], test_by_name) and test_temp not in ret: + ret.append(test_temp) + return ret + + +def get_applications_from_test(test): + ret = list() + if u'requirements' in test.keys() and u'duts' in test[u'requirements']: + for name, dut in test[u'requirements'][u'duts'].items(): + if u'application' in dut.keys() and u'name' in dut[u'application']: + ret.append(dut[u'application'][u'name']) + return ret + + +def get_application_list(icetea_json_output, tests_by_name): + """ Return comma separated list of application which are used in tests """ + ret = list() + for test in filter_test_by_name(icetea_json_output, tests_by_name): + ret.extend(get_applications_from_test(test)) + # Remove duplicates + return list(set(ret)) + + +def icetea_tests(target, tcdir, verbose): + command = ['icetea', '--tcdir', tcdir, '--list', '--json', '--platform_filter', target] \ + + (['-v'] if verbose else []) + + stdout, stderr, returncode = run_cmd(command) + + if returncode != 0: + raise Exception( + "Error when running icetea. \ncwd:{} \nCommand:'{}' \noutput:{}".format(os.getcwd(), ' '.join(command), + stderr.decode())) + + return json.loads(stdout) + + +def is_test_in_test_by_name(test_name, test_by_name): + for tbn_temp in test_by_name: + if re.search(translate(tbn_temp), test_name): + return True + return False + + +def check_tests(icetea_json_output): + """ + Check that all tests have all necessary information + :return: + """ + for test in icetea_json_output: + if not get_applications_from_test(test): + raise Exception('Test {} does not have application with correct name'.format(test['name'])) + + +def load_build_data(build_data_path): + """ + :return: build_data.json content as dict and None if build data is not available + """ + if not os.path.isfile(build_data_path): + return None + return json.load(open(build_data_path)) + + +if __name__ == '__main__': + try: + # Parse Options + parser = ArgumentParser() + + parser.add_argument('-m', '--mcu', + dest='target', + default=None, + help='Test target MCU', + required=True) + + parser.add_argument('-t', '--toolchain', + dest='toolchain', + default=None, + help='Toolchain', + required=True) + + parser.add_argument('--build-data', + dest='build_data', + default=None, + help='Detail data from build') + + parser.add_argument('--test-suite', + dest='test_suite', + default=None, + help='Path used for test suite file') + + parser.add_argument('-n', '--tests-by-name', + dest='tests_by_name', + default=None, + help='Limit the tests to a list (ex. test1,test2,test3)') + + parser.add_argument('--tcdir', + dest='tcdir', + default='TEST_APPS', + help='Test case directory', + required=False) + + parser.add_argument('--compile-list', + action='store_true', + dest='compile_list', + default=False, + help='List tests, which applications can be compiled') + + parser.add_argument('--run-list', + action='store_true', + dest='run_list', + default=False, + help='List tests, which applications are compiled and ready for run') + + parser.add_argument('--application-list', + action='store_true', + dest='application_list', + default=False, + help='List applications that need to be build') + + parser.add_argument('--ignore-checks', + action='store_true', + dest='ignore_checks', + default=False, + help='Ignore data validation checks') + + parser.add_argument('-v', '--verbose', + action='store_true', + dest='verbose', + default=False, + help='Verbose diagnostic output') + + options = parser.parse_args() + + icetea_json_output = icetea_tests(options.target, options.tcdir, options.verbose) + tests_by_name = options.tests_by_name.split(',') if options.tests_by_name else None + build_data = load_build_data(options.build_data) if options.build_data else None + + if not options.ignore_checks: + check_tests(icetea_json_output) + + if options.compile_list: + print('Available icetea tests for build \'{}-{}\', location \'{}\''.format( + options.target, options.toolchain, options.tcdir)) + for test in icetea_json_output: + print( + 'Test Case:\n Name: {name}\n Path: .{sep}{filepath}\n Test applications: .{sep}{apps}'.format( + name=test['name'], + sep=sep, + filepath=relpath(test['filepath'], ROOT), + apps=''.join(get_applications(test)).replace('-', os.path.sep))) + + elif options.run_list: + print('Available icetea tests for build \'{}-{}\', location \'{}\''.format( + options.target, options.toolchain, options.tcdir)) + + # Filters + tests = filter_test_by_name(icetea_json_output, tests_by_name) + if build_data: + tests = filter_test_by_build_data(tests, build_data, options.target, options.toolchain) + + for test in tests: + print(' test \'{name}\''.format(name=test['name'])) + + elif options.application_list: + print(','.join(get_application_list(icetea_json_output, tests_by_name))) + + else: + if not build_data: + raise Exception("Build data file does not exist: {}".format(options.build_data)) + + test_suite = create_test_suite(options.target, options.toolchain, icetea_json_output, build_data, + tests_by_name) + + if not test_suite['testcases']: + raise Exception("Test suite is empty. Check that --tcdir and --tests-by-name have correct values") + + if not options.test_suite: + raise Exception('--test-suite is required when running tests') + + with open(options.test_suite, 'w') as f: + json.dump(test_suite, f, indent=2) + + # List just for debug + if options.verbose: + cmd(['icetea', '--tcdir', options.tcdir, '--list'] + (['-v'] if options.verbose else [])) + + cmd(['icetea', '--tcdir', options.tcdir, '--suite', options.test_suite, '--clean', '--plugin_path', + plugins_path] + (['-v'] if options.verbose else [])) + + except KeyboardInterrupt as e: + print('\n[CTRL+c] exit') + except ConfigException as e: + # Catching ConfigException here to prevent a traceback + print('[ERROR] {}'.format(e)) + except Exception as e: + traceback.print_exc(file=sys.stdout) + print('[ERROR] {}'.format(e)) + sys.exit(1) diff --git a/tools/test.py b/tools/test.py index 179c7fb30b2..57af4cf3032 100644 --- a/tools/test.py +++ b/tools/test.py @@ -16,27 +16,28 @@ limitations under the License. -TEST BUILD & RUN +TEST BUILD """ from __future__ import print_function, division, absolute_import import sys import os -import json import fnmatch ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, ROOT) from tools.config import ConfigException, Config -from tools.test_api import test_path_to_name, find_tests, get_test_config, print_tests, build_tests, test_spec_from_test_builds from tools.test_configs import get_default_config +from tools.config import ConfigException +from tools.test_api import find_tests, get_test_config, print_tests, build_tests, test_spec_from_test_builds +import tools.test_configs as TestConfig from tools.options import get_default_options_parser, extract_profile, extract_mcus from tools.build_api import build_project, build_library from tools.build_api import print_build_memory_usage from tools.build_api import merge_build_data from tools.targets import TARGET_MAP from tools.notifier.term import TerminalNotifier -from tools.utils import mkdir, ToolException, NotSupportedException, args_error +from tools.utils import mkdir, ToolException, NotSupportedException, args_error, write_json_to_file from tools.test_exporters import ReportExporter, ResultExporterType from tools.utils import argparse_filestring_type, argparse_lowercase_type, argparse_many from tools.utils import argparse_dir_not_parent @@ -111,9 +112,19 @@ dest="stats_depth", default=2, help="Depth level for static memory report") - parser.add_argument("--ignore", dest="ignore", type=argparse_many(str), default=None, help="Comma separated list of patterns to add to mbedignore (eg. ./main.cpp)") + parser.add_argument("--icetea", + action="store_true", + dest="icetea", + default=False, + help="Only icetea tests") + + parser.add_argument("--greentea", + action="store_true", + dest="greentea", + default=False, + help="Only greentea tests") options = parser.parse_args() @@ -126,8 +137,13 @@ all_tests = {} tests = {} + # As default both test tools are enabled + if not (options.greentea or options.icetea): + options.greentea = True + options.icetea = True + # Target - if options.mcu is None : + if options.mcu is None: args_error(parser, "argument -m/--mcu is required") mcu = extract_mcus(parser, options)[0] @@ -159,8 +175,13 @@ # Find all tests in the relevant paths for path in all_paths: - all_tests.update(find_tests(path, mcu, toolchain, - app_config=config)) + all_tests.update(find_tests( + base_dir=path, + target_name=mcu, + toolchain_name=toolchain, + icetea=options.icetea, + greentea=options.greentea, + app_config=config)) # Filter tests by name if specified if options.names: @@ -251,20 +272,7 @@ # If a path to a test spec is provided, write it to a file if options.test_spec: - test_spec_data = test_spec_from_test_builds(test_build) - - # Create the target dir for the test spec if necessary - # mkdir will not create the dir if it already exists - test_spec_dir = os.path.dirname(options.test_spec) - if test_spec_dir: - mkdir(test_spec_dir) - - try: - with open(options.test_spec, 'w') as f: - f.write(json.dumps(test_spec_data, indent=2)) - except IOError as e: - print("[ERROR] Error writing test spec to file") - print(e) + write_json_to_file(test_spec_from_test_builds(test_build), options.test_spec) # If a path to a JUnit build report spec is provided, write it to a file if options.build_report_junit: @@ -296,3 +304,4 @@ traceback.print_exc(file=sys.stdout) print("[ERROR] %s" % str(e)) sys.exit(1) + diff --git a/tools/test/__init__.py b/tools/test/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/test/run_icetea/TEST_DIR/__init__.py b/tools/test/run_icetea/TEST_DIR/__init__.py new file mode 100644 index 00000000000..ecb93799a76 --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR/__init__.py @@ -0,0 +1,14 @@ +""" +Copyright 2017 ARM Limited +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. +""" \ No newline at end of file diff --git a/tools/test/run_icetea/TEST_DIR/test_pass.py b/tools/test/run_icetea/TEST_DIR/test_pass.py new file mode 100644 index 00000000000..f834ea99c46 --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR/test_pass.py @@ -0,0 +1,37 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from icetea_lib.bench import Bench + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="test_pass", + title="Test icetea integration", + status="released", + purpose="Just for testing scripts", + component=[], + type="smoke" + ) + + def setup(self): + pass + + def case(self): + print("Test2 running") + + def teardown(self): + pass diff --git a/tools/test/run_icetea/TEST_DIR/test_print.py b/tools/test/run_icetea/TEST_DIR/test_print.py new file mode 100644 index 00000000000..b5158b4e7b7 --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR/test_print.py @@ -0,0 +1,37 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from icetea_lib.bench import Bench + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="test_print", + title="Test icetea integration", + status="released", + purpose="Just for testing scripts", + component=[], + type="smoke" + ) + + def setup(self): + pass + + def case(self): + print("Test running") + + def teardown(self): + pass diff --git a/tools/test/run_icetea/TEST_DIR_HW/README.md b/tools/test/run_icetea/TEST_DIR_HW/README.md new file mode 100644 index 00000000000..6f59e5747bf --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR_HW/README.md @@ -0,0 +1 @@ +This folder contains hardware test data for icetea integration \ No newline at end of file diff --git a/tools/test/run_icetea/TEST_DIR_HW/__init__.py b/tools/test/run_icetea/TEST_DIR_HW/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/test/run_icetea/TEST_DIR_HW/test_K64F.py b/tools/test/run_icetea/TEST_DIR_HW/test_K64F.py new file mode 100644 index 00000000000..f25ddf8d569 --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR_HW/test_K64F.py @@ -0,0 +1,49 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from icetea_lib.bench import Bench + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="test_K64F_only", + title="Test a test case which have only K64F support", + status="released", + purpose="Just for testing scripts", + component=[], + type="smoke", + requirements={ + "duts": { + '*': { + "count": 1, + "type": "hardware", + "allowed_platforms": ['K64F'], + "application": { + "name": "TEST_APPS-device-exampleapp" + } + } + } + } + ) + + def setup(self): + pass + + def case(self): + pass + + def teardown(self): + pass diff --git a/tools/test/run_icetea/TEST_DIR_HW/test_predefined_platforms.py b/tools/test/run_icetea/TEST_DIR_HW/test_predefined_platforms.py new file mode 100644 index 00000000000..8e5145b25cb --- /dev/null +++ b/tools/test/run_icetea/TEST_DIR_HW/test_predefined_platforms.py @@ -0,0 +1,58 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from icetea_lib.bench import Bench + + +class Testcase(Bench): + def __init__(self): + Bench.__init__(self, + name="test_predefined_platforms", + title="Test a test case which have support for multiple platforms", + status="released", + purpose="Just for testing scripts", + component=[], + type="regression", + requirements={ + "duts": { + '*': { + "count": 1, + "type": "hardware", + "allowed_platforms": [ + "LPC1768", "KL25Z", "K64F", "K66F", "K22F", "LPC4088", "LPC1549", + "NUCLEO_F072RB", "NUCLEO_F091RC", "NUCLEO_F302R8", "NUCLEO_F303K8", + "NUCLEO_F303RE", "NUCLEO_F207ZG", "NUCLEO_F334R8", "NUCLEO_F303ZE", + "NUCLEO_L053R8", "DISCO_L072CZ_LRWAN1", "NUCLEO_L073RZ", "NUCLEO_L152RE", + "NUCLEO_F410RB", "NUCLEO_F446RE", "NUCLEO_F446ZE", "NUCLEO_F429ZI", + "DISCO_F407VG", "NUCLEO_F746ZG", "NUCLEO_L476RG", "DISCO_L053C8", "DISCO_F334C8", + "DISCO_L475VG_IOT01A", "DISCO_L476VG", "DISCO_F469NI", "DISCO_F429ZI", + "DISCO_F769NI", "ARCH_MAX", "MAX32600MBED", "MOTE_L152RC", "B96B_F446VE" + ], + "application": { + "name": "TEST_APPS-device-exampleapp" + } + } + } + } + ) + + def setup(self): + pass + + def case(self): + pass + + def teardown(self): + pass diff --git a/tools/test/run_icetea/__init__.py b/tools/test/run_icetea/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/test/run_icetea/empty_build_data.json b/tools/test/run_icetea/empty_build_data.json new file mode 100644 index 00000000000..7292e11b6cc --- /dev/null +++ b/tools/test/run_icetea/empty_build_data.json @@ -0,0 +1,3 @@ +{ + "builds": [] +} \ No newline at end of file diff --git a/tools/test/run_icetea/run_icetea_test.py b/tools/test/run_icetea/run_icetea_test.py new file mode 100644 index 00000000000..393a3d0262a --- /dev/null +++ b/tools/test/run_icetea/run_icetea_test.py @@ -0,0 +1,102 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +from os.path import realpath, join, dirname, isfile +import subprocess + +""" +Tests for run_icetea.py +""" +this_file_dir = dirname(realpath(__file__)) +hw_test_dir = join(this_file_dir, 'TEST_DIR_HW') +test_dir = join(this_file_dir, 'TEST_DIR') +empty_build_data = join(this_file_dir, 'empty_build_data.json') +test_suite = join(this_file_dir, 'test_suite.json') +run_icetea_py = join(dirname(dirname(this_file_dir)), 'run_icetea.py') +assert isfile(run_icetea_py) + + +def _execute_icetea(*params): + command = ["python", run_icetea_py] + list(params) + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stout, sterr = process.communicate() + status = process.poll() + if status != 0: + raise Exception("Error with {}, \nreturn code: {}, \nerror message: {}, \noutput:{}".format( + " ".join(command), status, sterr, stout + )) + return stout.decode() + + +def test_help(): + """ + Just test that something works + :return: + """ + _execute_icetea('--help') + + +def test_list_tests_k64f(): + out = _execute_icetea('--compile-list', '--mcu', 'K64F', '--toolchain', 'GCC_ARM', '--tcdir', hw_test_dir) + assert 'test_K64F_only' in out + assert 'test_predefined_platforms' in out + + +def test_list_tests_nucleo_l073rz(): + out = _execute_icetea('--compile-list', '--mcu', 'NUCLEO_L073RZ', '--toolchain', 'GCC_ARM', '--tcdir', hw_test_dir) + assert 'test_predefined_platforms' in out + assert 'test_K64F_only' not in out + + +def test_run(): + out = _execute_icetea('--mcu', 'K64F', '--toolchain', 'GCC_ARM', '--tcdir', test_dir, '--build-data', + empty_build_data, '--test-suite', test_suite, '--ignore-checks') + assert 'test_print' in out + assert 'test_pass' in out + + +def test_run_by_name(): + out = _execute_icetea('--mcu', 'K64F', '--toolchain', 'GCC_ARM', '--tcdir', test_dir, '--build-data', + empty_build_data, '--test-suite', test_suite, '--tests-by-name', 'test_pass', + '--ignore-checks') + assert 'test_pass' in out + assert 'test_print' not in out + + +def test_run_hw_with_not_build_tests(): + """ + When test binaries are not found tests will be skipped + :return: + """ + out = _execute_icetea('--mcu', 'K64F', '--toolchain', 'GCC_ARM', '--tcdir', hw_test_dir, '--build-data', + empty_build_data, '--test-suite', test_suite) + output_lines = out.split('\n') + + # Assert that + temp = list(filter(lambda x: 'test_K64F_only' in x, output_lines))[0] + assert 'skip' in temp + + temp = list(filter(lambda x: 'test_predefined_platforms' in x, output_lines))[0] + assert 'skip' in temp + + +def test_data_validation(): + exception_happened = False + try: + _execute_icetea('--mcu', 'K64F', '--toolchain', 'GCC_ARM', '--tcdir', test_dir, '--build-data', + empty_build_data, '--test-suite', test_suite) + except BaseException: + exception_happened = True + assert exception_happened diff --git a/tools/test/run_icetea/run_icetea_unittest.py b/tools/test/run_icetea/run_icetea_unittest.py new file mode 100644 index 00000000000..052f5a40132 --- /dev/null +++ b/tools/test/run_icetea/run_icetea_unittest.py @@ -0,0 +1,144 @@ +""" +Copyright 2018 ARM Limited +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. +""" + +import os +import sys + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", + "..")) +sys.path.insert(0, ROOT) + +from tools.run_icetea import find_build_from_build_data, filter_test_by_build_data, filter_test_by_name, \ + get_application_list + +""" +Unit tests for run_icetea.py +""" + +test_build_data = { + 'builds': [ + { + "id": "TEST_APPS-DEVICE-SOCKET_APP", + "target_name": "K64F", + "toolchain_name": "GCC_ARM" + } + ] +} + + +def test_find_build_from_build_data_empty(): + assert find_build_from_build_data(build_data={'builds': []}, id="something", target="K64F", + toolchain="GCC_ARM") is None + + +def test_find_build_from_build_data_wrong_target(): + assert find_build_from_build_data(build_data=test_build_data, id="TEST_APPS-DEVICE-SOCKET_APP", target="AAAA", + toolchain="GCC_ARM") is None + + +def test_find_build_from_build_data(): + assert find_build_from_build_data(build_data=test_build_data, id="TEST_APPS-DEVICE-SOCKET_APP", target="K64F", + toolchain="GCC_ARM") is not None + + +icetea_json_output = [ + { + "status": "released", + "requirements": { + "duts": { + "1": { + "nick": "dut1" + }, + "*": { + "count": 1, + "application": { + "bin": None, + "name": "TEST_APPS-device-socket_app" + }, + "type": "hardware" + } + }, + "external": { + "apps": [] + } + }, + "name": "UDPSOCKET_BIND_PORT", + "filepath": "/Users/test/mbed-os/TEST_APPS/testcases/SOCKET_BIND_PORT.py", + "title": "udpsocket open and bind port", + "component": [ + "mbed-os", + "netsocket" + ], + "compatible": { + "framework": { + "version": ">=1.0.0", + "name": "Icetea" + }, + "hw": { + "value": True + }, + "automation": { + "value": True + } + }, + "subtype": "socket", + "purpose": "Verify UDPSocket can be created, opened and port binded", + "type": "smoke", + "sub_type": None + } +] + + +def test_filter_test_by_build_data_when_data_is_empty(): + assert filter_test_by_build_data( + icetea_json_output=icetea_json_output, + build_data=None, + target="K64F", + toolchain="GCC_ARM" + ) == icetea_json_output + + +def test_filter_test_by_build_data(): + temp = filter_test_by_build_data( + icetea_json_output=icetea_json_output, + build_data=test_build_data, + target="K64F", + toolchain="GCC_ARM" + ) + assert len(temp) > 0 + + +def test_filter_test_by_name(): + assert len(filter_test_by_name(icetea_json_output, ['UDPSOCKET_BIND_PORT'])) > 0 + + +def test_filter_test_by_name_when_not_found(): + assert filter_test_by_name(icetea_json_output, ['AAA']) == list() + + +def test_filter_test_by_name_when_name_is_empty(): + assert filter_test_by_name(icetea_json_output, None) == icetea_json_output + + +def test_get_application_list(): + assert 'TEST_APPS-device-socket_app' in get_application_list(icetea_json_output, ['UDPSOCKET_BIND_PORT']) + + +def test_get_application_list_not_found(): + assert 'TEST_APPS-device-socket_app' not in get_application_list(icetea_json_output, ['SOMETHING_ELSE']) + + +def test_get_application_list_none(): + assert 'TEST_APPS-device-socket_app' in get_application_list(icetea_json_output, None) diff --git a/tools/test_api.py b/tools/test_api.py index 121b18016c4..848b5f84058 100644 --- a/tools/test_api.py +++ b/tools/test_api.py @@ -2066,13 +2066,15 @@ def get_test_config(config_name, target_name): # Otherwise find the path to configuration file based on mbed OS interface return TestConfig.get_config_path(config_name, target_name) -def find_tests(base_dir, target_name, toolchain_name, app_config=None): + +def find_tests(base_dir, target_name, toolchain_name, icetea, greentea, app_config=None): """ Finds all tests in a directory recursively - base_dir: path to the directory to scan for tests (ex. 'path/to/project') - target_name: name of the target to use for scanning (ex. 'K64F') - toolchain_name: name of the toolchain to use for scanning (ex. 'GCC_ARM') - options: Compile options to pass to the toolchain (ex. ['debug-info']) - app_config - location of a chosen mbed_app.json file + :param base_dir: path to the directory to scan for tests (ex. 'path/to/project') + :param target_name: name of the target to use for scanning (ex. 'K64F') + :param toolchain_name: name of the toolchain to use for scanning (ex. 'GCC_ARM') + :param icetea: icetea enabled + :param greentea: greentea enabled + :param app_config - location of a chosen mbed_app.json file returns a dictionary where keys are the test name, and the values are lists of paths needed to biuld the test. @@ -2089,38 +2091,56 @@ def find_tests(base_dir, target_name, toolchain_name, app_config=None): base_resources = Resources(MockNotifier(), collect_ignores=True) base_resources.scan_with_config(base_dir, config) - dirs = [d for d in base_resources.ignored_dirs if basename(d) == 'TESTS'] - ignoreset = MbedIgnoreSet() + if greentea: + dirs = [d for d in base_resources.ignored_dirs if basename(d) == 'TESTS'] + ignoreset = MbedIgnoreSet() + + for directory in dirs: + ignorefile = join(directory, IGNORE_FILENAME) + if isfile(ignorefile): + ignoreset.add_mbedignore(directory, ignorefile) + for test_group_directory in os.listdir(directory): + grp_dir = join(directory, test_group_directory) + if not isdir(grp_dir) or ignoreset.is_ignored(grp_dir): + continue + grpignorefile = join(grp_dir, IGNORE_FILENAME) + if isfile(grpignorefile): + ignoreset.add_mbedignore(grp_dir, grpignorefile) + for test_case_directory in os.listdir(grp_dir): + d = join(directory, test_group_directory, test_case_directory) + if not isdir(d) or ignoreset.is_ignored(d): + continue + special_dirs = ['host_tests', 'COMMON'] + if test_group_directory not in special_dirs and test_case_directory not in special_dirs: + test_name = test_path_to_name(d, base_dir) + tests[(test_name, directory, test_group_directory, test_case_directory)] = [d] + if test_case_directory == 'COMMON': + def predicate(base_pred, group_pred, name_base_group_case): + (name, base, group, case) = name_base_group_case + return base == base_pred and group == group_pred + + commons.append((functools.partial(predicate, directory, test_group_directory), d)) + if test_group_directory == 'COMMON': + def predicate(base_pred, name_base_group_case): + (name, base, group, case) = name_base_group_case + return base == base_pred + + commons.append((functools.partial(predicate, directory), grp_dir)) - for directory in dirs: - ignorefile = join(directory, IGNORE_FILENAME) - if isfile(ignorefile): - ignoreset.add_mbedignore(directory, ignorefile) - for test_group_directory in os.listdir(directory): - grp_dir = join(directory, test_group_directory) - if not isdir(grp_dir) or ignoreset.is_ignored(grp_dir): + if icetea: + dirs = [d for d in base_resources.ignored_dirs if basename(d) == 'TEST_APPS'] + for directory in dirs: + if not isdir(directory): continue - grpignorefile = join(grp_dir, IGNORE_FILENAME) - if isfile(grpignorefile): - ignoreset.add_mbedignore(grp_dir, grpignorefile) - for test_case_directory in os.listdir(grp_dir): - d = join(directory, test_group_directory, test_case_directory) - if not isdir(d) or ignoreset.is_ignored(d): + for subdir in os.listdir(directory): + d = join(directory, subdir) + if not isdir(d): continue - special_dirs = ['host_tests', 'COMMON'] - if test_group_directory not in special_dirs and test_case_directory not in special_dirs: - test_name = test_path_to_name(d, base_dir) - tests[(test_name, directory, test_group_directory, test_case_directory)] = [d] - if test_case_directory == 'COMMON': - def predicate(base_pred, group_pred, name_base_group_case): - (name, base, group, case) = name_base_group_case - return base == base_pred and group == group_pred - commons.append((functools.partial(predicate, directory, test_group_directory), d)) - if test_group_directory == 'COMMON': - def predicate(base_pred, name_base_group_case): - (name, base, group, case) = name_base_group_case - return base == base_pred - commons.append((functools.partial(predicate, directory), grp_dir)) + if 'device' == subdir: + for test_dir in os.listdir(d): + test_dir_path = join(d, test_dir) + test_name = test_path_to_name(test_dir_path, base_dir) + tests[(test_name, directory, subdir, test_dir)] = [test_dir_path] # Apply common directories for pred, path in commons: @@ -2131,6 +2151,7 @@ def predicate(base_pred, name_base_group_case): # Drop identity besides name return {name: paths for (name, _, _, _), paths in six.iteritems(tests)} + def print_tests(tests, format="list", sort=True): """Given a dictionary of tests (as returned from "find_tests"), print them in the specified format""" @@ -2235,7 +2256,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, "base_path": base_path, "baud_rate": baud_rate, "binary_type": "bootable", - "tests": {} + "tests": {}, + "test_apps": {} } result = True @@ -2314,7 +2336,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, 'bin_file' in worker_result): bin_file = norm_relative_path(worker_result['bin_file'], execution_directory) - test_build['tests'][worker_result['kwargs']['project_id']] = { + test_key = 'test_apps' if 'test_apps-' in worker_result['kwargs']['project_id'] else 'tests' + test_build[test_key][worker_result['kwargs']['project_id']] = { "binaries": [ { "path": bin_file @@ -2357,4 +2380,4 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, def test_spec_from_test_builds(test_builds): return { "builds": test_builds - } + } \ No newline at end of file diff --git a/tools/test_configs/config_paths.json b/tools/test_configs/config_paths.json index 34244ca98f5..0921610a6a9 100644 --- a/tools/test_configs/config_paths.json +++ b/tools/test_configs/config_paths.json @@ -3,6 +3,9 @@ "HEAPBLOCKDEVICE": "HeapBlockDevice.json", "HEAPBLOCKDEVICE_AND_ETHERNET": "HeapBlockDeviceAndEthernetInterface.json", "HEAPBLOCKDEVICE_AND_WIFI": "HeapBlockDeviceAndWifiInterface.json", + "ODIN_WIFI" : "OdinInterface.json", + "ODIN_ETHERNET" : "Odin_EthernetInterface.json", + "REALTEK_WIFI" : "RealtekInterface.json", "ESP8266_WIFI" : "ESP8266Interface.json", "ISM43362_WIFI" : "ISM43362Interface.json", "IDW0XX1_WIFI" : "SpwfSAInterface.json", diff --git a/tools/test_configs/target_configs.json b/tools/test_configs/target_configs.json index 85af0aebac4..e74e6dbe0d7 100644 --- a/tools/test_configs/target_configs.json +++ b/tools/test_configs/target_configs.json @@ -9,11 +9,11 @@ }, "K64F": { "default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET", - "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET", "ESP8266_WIFI", "ETHERNET"] + "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET", "MAC_TESTER", "ESP8266_WIFI", "ETHERNET"] }, "NUCLEO_F429ZI": { "default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET", - "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET"] + "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET", "MAC_TESTER"] }, "DISCO_L475VG_IOT01A": { "default_test_configuration": "NONE", diff --git a/tools/utils.py b/tools/utils.py index 640403fe008..93e70430a0a 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -179,6 +179,27 @@ def mkdir(path): makedirs(path) +def write_json_to_file(json_data, file_name): + """ + Write json content in file + :param json_data: + :param file_name: + :return: + """ + # Create the target dir for file if necessary + test_spec_dir = os.path.dirname(file_name) + + if test_spec_dir: + mkdir(test_spec_dir) + + try: + with open(file_name, 'w') as f: + f.write(json.dumps(json_data, indent=2)) + except IOError as e: + print("[ERROR] Error writing test spec to file") + print(e) + + def copy_file(src, dst): """ Implement the behaviour of "shutil.copy(src, dst)" without copying the permissions (this was causing errors with directories mounted with samba)