mirror of https://github.com/ARMmbed/mbed-os.git
Icetea support
parent
2e869df296
commit
e27a26eb9d
|
@ -88,3 +88,6 @@ tags
|
|||
.vscode/
|
||||
|
||||
features/FEATURE_BLE/targets/TARGET_CORDIO/stack_backup/
|
||||
|
||||
.pytest_cache
|
||||
log
|
|
@ -0,0 +1,2 @@
|
|||
test/*
|
||||
source/ns_list_internal/*
|
|
@ -0,0 +1,5 @@
|
|||
SRCS := $(wildcard source/*.c)
|
||||
LIB := libCmdline.a
|
||||
EXPORT_HEADERS := mbed-client-cli
|
||||
|
||||
include ../exported_rules.mk
|
105
README.md
105
README.md
|
@ -33,3 +33,108 @@ We have a [developer website](https://os.mbed.com) for asking questions, engagin
|
|||
## Getting started for contributors
|
||||
|
||||
We also have a [contributing and publishing guide](https://os.mbed.com/contributing/) that covers licensing, contributor agreements and style guidelines.
|
||||
=======
|
||||
# mbed-client-cli
|
||||
|
||||
This is the Command Line Library for a CLI application. This library provides methods for:
|
||||
|
||||
* Adding commands to the interpreter.
|
||||
* Deleting commands from the interpreter.
|
||||
* Executing commands.
|
||||
* Adding command aliases to the interpreter.
|
||||
* Searching command arguments.
|
||||
|
||||
## API
|
||||
|
||||
Command Line Library API is described in the snipplet below:
|
||||
|
||||
```c++
|
||||
// if thread safety for CLI terminal output is needed
|
||||
// configure output mutex wait cb before initialization so it's available immediately
|
||||
cmd_set_mutex_wait_func( (func)(void) );
|
||||
// configure output mutex release cb before initialization so it's available immediately
|
||||
cmd_set_mutex_wait_func( (func)(void) );
|
||||
// initialize cmdline with print function
|
||||
cmd_init( (func)(const char* fmt, va_list ap) );
|
||||
// configure ready cb
|
||||
cmd_set_ready_cb( (func)(int retcode) );
|
||||
// register command for library
|
||||
cmd_add( <command>, (int func)(int argc, char *argv[]), <help>, <man>);
|
||||
//execute some existing commands
|
||||
cmd_exe( <command> );
|
||||
```
|
||||
|
||||
## Tracing
|
||||
|
||||
Command Line Library has trace messages, which are disabled by default.
|
||||
"MBED_CLIENT_CLI_TRACE_ENABLE" flag if defined, enables all the trace prints for debugging.
|
||||
|
||||
## Usage example
|
||||
|
||||
Adding new commands to the Command Line Library and executing the commands:
|
||||
|
||||
```c++
|
||||
//example print function
|
||||
void myprint(const char* fmt, va_list ap){ vprintf(fmt, ap); }
|
||||
|
||||
// ready cb, calls next command to be executed
|
||||
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, that needs for example some events to complete the command execution
|
||||
int cmd_long(int argc, char *argv[] ) {
|
||||
timer_start( 5000, timer_ready_cb ); //pseudo timer code
|
||||
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( "dummy;long" );
|
||||
}
|
||||
```
|
||||
|
||||
## Thread safety
|
||||
The CLI library is not thread safe, but the CLI terminal output can be locked against other
|
||||
output streams, for example if both traces and CLI output are using serial out.
|
||||
|
||||
```c++
|
||||
static Mutex MyMutex;
|
||||
|
||||
// mutex wait cb, acquires the mutex, waiting if necessary
|
||||
static void mutex_wait(void)
|
||||
{
|
||||
MyMutex.lock();
|
||||
}
|
||||
|
||||
// mutex release cb, releases the mutex
|
||||
static void my_mutex_release(void)
|
||||
{
|
||||
MyMutex.unlock();
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
cmd_mutex_wait_func( my_mutex_wait ); // configure mutex wait function before initializing
|
||||
cmd_mutex_release_func( my_mutex_release ); // configure mutex release function before initializing
|
||||
cmd_init( &myprint ); // initialize cmdline with print function
|
||||
cmd_set_ready_cb( cmd_ready_cb ); // configure ready cb.
|
||||
//CLI terminal output now locks against MyMutex
|
||||
}
|
||||
|
||||
```
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2015-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 <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "mbed.h"
|
||||
#include "mbed-client-cli/ns_cmdline.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)
|
||||
|
||||
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, 115200);
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
#include "NetworkStack.h"
|
||||
#include "NetworkInterface.h"
|
||||
|
||||
#include "mbed-client-cli/ns_cmdline.h"
|
||||
#include "mbed-trace/mbed_trace.h"
|
||||
|
||||
#ifndef MBED_CONF_APP_CONNECT_STATEMENT
|
||||
#error [NOT_SUPPORTED] No network configuration found for this target.
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include MBED_CONF_APP_HEADER_FILE
|
||||
|
||||
#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 bool is_ipv4(const char *str)
|
||||
{
|
||||
int dot_count = 0;
|
||||
for (int i = 0; str[i]; i++) {
|
||||
if (str[i] == '.' && ++dot_count == 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ifconfig_print()
|
||||
{
|
||||
if(!net)
|
||||
return;
|
||||
const char *str = net->get_ip_address();
|
||||
|
||||
if (str) {
|
||||
if (is_ipv4(str)) {
|
||||
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 = MBED_CONF_APP_OBJECT_CONSTRUCTION;
|
||||
int err = MBED_CONF_APP_CONNECT_STATEMENT;
|
||||
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";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
|||
#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
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2015-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 <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#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)
|
||||
|
||||
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, 115200);
|
||||
#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;
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2016 ARM Limited. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#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 "<err>";
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2016 ARM Limited. All rights reserved.
|
||||
*/
|
||||
|
||||
#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
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
#
|
||||
# Copyright (c) 2018, Arm Limited and affiliates.
|
||||
#
|
||||
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
|
|
@ -0,0 +1,8 @@
|
|||
#
|
||||
# Copyright (c) 2018, Arm Limited and affiliates.
|
||||
#
|
||||
from ip_test_parsers import IpTestParsers
|
||||
|
||||
plugins_to_load = {
|
||||
"ip_test_parsers": IpTestParsers
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
## 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 and test applications have a dependency
|
||||
|
||||
### Preparing your work environment
|
||||
|
||||
#### Prerequisities
|
||||
|
||||
You should have IceTea and forked 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.
|
||||
|
||||
### 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 <target> -t <toolchain> --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 <target> -t <toolchain> --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 <toolchain> --icetea --test-config tools/test-configs/OdinInterface.json`
|
||||
|
||||
To run the tests with ethernet interface:
|
||||
`>mbed test -m UBLOX_EVK_ODIN_W2 -t <toolchain> --icetea --test-config tools/test-configs/Odin_EthernetInterface.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. Depending on the used interface you might need to provide ssid, password and security.
|
||||
You can either provide separate WIFI_SSID, WIFI_PASSWORD and WIFI_SECURITY macros, or provide the SSID, password and security in the connect statement provided in the test-config -file.
|
||||
|
||||
Example of separate macros:
|
||||
```
|
||||
"wifi-ssid": {
|
||||
"help": "WiFi SSID",
|
||||
"value": "\"ssid\"",
|
||||
"macro_name": "WIFI_SSID"
|
||||
},
|
||||
"wifi-password": {
|
||||
"help": "WiFi Password",
|
||||
"value": "\"password\"",
|
||||
"macro_name": "WIFI_PASSWORD"
|
||||
},
|
||||
"wifi-security": {
|
||||
"help": "WiFi Security, values from nsapi_security from features/netsocket/nsapi_types.h"
|
||||
"value": "NSAPI_SECURITY_WPA_WPA2"
|
||||
"macro_name": "WIFI_SECURITY"
|
||||
```
|
||||
|
||||
Example of modifying the connect statement
|
||||
Original:
|
||||
```
|
||||
"connect-statement" : {
|
||||
"help" : "Must use 'net' variable name",
|
||||
"value" : "net->wifiInterface()->connect(WIFI_SSID, WIFI_PASSWORD, WIFI_SECURITY)"
|
||||
},
|
||||
```
|
||||
Modified:
|
||||
```
|
||||
"connect-statement" : {
|
||||
"help" : "Must use 'net' variable name",
|
||||
"value" : "net->wifiInterface()->connect(\"ssid\", \"password\", NSAPI_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 from 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.
|
|
@ -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):
|
||||
self.device = None
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self.device = self.get_node_endpoint(1)
|
||||
|
||||
def case(self):
|
||||
self.command(1, "echo hello world", timeout=5)
|
||||
self.device.command("help")
|
||||
|
||||
def teardown(self):
|
||||
pass
|
|
@ -0,0 +1,60 @@
|
|||
#
|
||||
# Copyright (c) 2017-2018, Arm Limited and affiliates.
|
||||
#
|
||||
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")
|
|
@ -0,0 +1,90 @@
|
|||
#
|
||||
# Copyright (c) 2017-2018, Arm Limited and affiliates.
|
||||
#
|
||||
from icetea_lib.bench import Bench
|
||||
from interface import interfaceUp, interfaceDown
|
||||
from icetea_lib.tools import test_case
|
||||
import threading
|
||||
import time
|
||||
|
||||
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 <juha.ylinen@arm.com>",
|
||||
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"])
|
|
@ -0,0 +1,68 @@
|
|||
#
|
||||
# Copyright (c) 2018, Arm Limited and affiliates.
|
||||
#
|
||||
from icetea_lib.bench import Bench, TestStepFail
|
||||
from icetea_lib.tools import test_case
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
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 <juha.ylinen@arm.com>",
|
||||
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 randomString(self, length):
|
||||
return ''.join(random.choice(string.ascii_uppercase) for i in range(length))
|
||||
|
||||
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 = self.randomString(size)
|
||||
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")
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# Copyright (c) 2017, Arm Limited and affiliates.
|
||||
#
|
||||
import random, string
|
||||
|
||||
'''
|
||||
This script is intended to be a common test data generator.
|
||||
Currently it implements randomUppercaseAsciiString, randomLowercaseAsciiString methods.
|
||||
'''
|
||||
|
||||
def randomUppercaseAsciiString(length):
|
||||
return ''.join(random.choice(string.ascii_uppercase) for i in range(length))
|
||||
|
||||
|
||||
def randomLowercaseAsciiString(length):
|
||||
return ''.join(random.choice(string.ascii_lowercase) for i in range(length))
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# Copyright (c) 2016-2018, Arm Limited and affiliates.
|
||||
#
|
||||
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")
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* 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
|
||||
* \startuml{cli_usecase.png}
|
||||
actor user
|
||||
participant app
|
||||
participant cli
|
||||
participant mycmd
|
||||
|
||||
== Initialization ==
|
||||
* app -> cli: cmd_init( &default_cmd_response_out )
|
||||
note right : This initialize command line library
|
||||
* mycmd -> cli: cmd_add( mycmd_command, "mycmd", NULL, NULL)
|
||||
note right : All commands have to be register \nto cmdline library with cmd_add() function
|
||||
|
||||
== command input characters==
|
||||
* app -> cli: cmd_char_input("d")
|
||||
* app -> cli: cmd_char_input("u")
|
||||
* app -> cli: cmd_char_input("m")
|
||||
* app -> cli: cmd_char_input("m")
|
||||
* app -> cli: cmd_char_input("y")
|
||||
* app -> cli: cmd_char_input("\\n")
|
||||
note left : User write command to \nbe execute and press ENTER when \ncommand with all parameters are ready.\nCharacters can be come from serial port for example.
|
||||
== command execution==
|
||||
* mycmd <- cli: mycmd_command(argc, argv)
|
||||
* mycmd -> cli: cmd_printf("hello world!\\n")
|
||||
note right : cmd_printf() should \nbe used when command prints \nsomething to the console
|
||||
* cli -> user: "hello world!\\n"
|
||||
* mycmd -> cli: <<retcode>>
|
||||
== finish command and goto forward ==
|
||||
* app <- cli: cmd_ready_cb()
|
||||
* app -> cli: cmd_next()
|
||||
note left : this way application can \ndecide when/how to go forward.\nWhen using event-loop, \nyou probably want create tasklet where \ncommands are actually executed.\nif there are some commands in queue cmd_next()\nstart directly next command execution.\n
|
||||
== command execution==
|
||||
* app -> cli: cmd_exe("long\\n")
|
||||
note left : input string can be \ngive also with cmd_exe() -function
|
||||
* mycmd <- cli: long_command(argc, argv)
|
||||
* mycmd -> cli: <<retcode>> = CMDLINE_RETCODE_EXECUTING_CONTINUE
|
||||
note right : When command continue in background, it should return\nCMDLINE_RETCODE_EXECUTING_CONTINUE.\nCommand interpreter not continue next command \nas long as cmd_ready() -function is not called.
|
||||
... Some ~~long delay~~ ...
|
||||
* mycmd -> cli: cmd_ready( <<retcode>> )
|
||||
note right : When command is finally finished,\nit should call cmd_ready() function.
|
||||
== finish command and goto forward ==
|
||||
* app <- cli: cmd_ready_cb()
|
||||
* app -> cli: cmd_next()
|
||||
... ...
|
||||
* \enduml
|
||||
*/
|
||||
#ifndef _CMDLINE_H_
|
||||
#define _CMDLINE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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_*/
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "mbed-client-cli",
|
||||
"version": "0.3.0",
|
||||
"description": "Command Line Library for mbedOS",
|
||||
"keywords": [
|
||||
"cli",
|
||||
"client",
|
||||
"cmd"
|
||||
],
|
||||
"author": "Jussi Vatjus-Anttila",
|
||||
"repository": {
|
||||
"url": "https://github.com/ARMmbed/mbed-client-cli.git",
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/ARMmbed/mbed-client-cli.git",
|
||||
"licenses": [
|
||||
{
|
||||
"url": "https://spdx.org/licenses/Apache-2.0",
|
||||
"type": "Apache-2.0"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"mbed-trace": "^1.1.2"
|
||||
},
|
||||
"testTargetDependencies": {
|
||||
"x86-linux-native": {
|
||||
"cpputest": "ARMmbed/cpputest"
|
||||
},
|
||||
"x86-windows-native": {
|
||||
"cpputest": "ARMmbed/cpputest"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
if(DEFINED TARGET_LIKE_X86_LINUX_NATIVE)
|
||||
add_library( mbed-client-cli
|
||||
ns_cmdline.c
|
||||
ns_list_internal/ns_list.c
|
||||
)
|
||||
add_definitions("-g -O0 -fprofile-arcs -ftest-coverage")
|
||||
target_link_libraries(mbed-client-cli gcov)
|
||||
else()
|
||||
add_library( mbed-client-cli
|
||||
ns_cmdline.c
|
||||
ns_list_internal/ns_list.c
|
||||
)
|
||||
target_link_libraries(mbed-client-cli)
|
||||
endif()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* All functions can be inlined, and definitions are in ns_list.h.
|
||||
* Define NS_LIST_FN before including it to generate external definitions.
|
||||
*/
|
||||
#define NS_LIST_FN extern
|
||||
|
||||
#include "ns_list.h"
|
|
@ -0,0 +1,721 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef NS_LIST_H_
|
||||
#define NS_LIST_H_
|
||||
|
||||
#include "ns_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/** \file
|
||||
* \brief Linked list support library
|
||||
*
|
||||
* The ns_list.h file provides a doubly-linked list/queue, providing O(1)
|
||||
* performance for all insertion/removal operations, and access to either
|
||||
* end of the list.
|
||||
*
|
||||
* Memory footprint is two pointers for the list head, and two pointers in each
|
||||
* list entry. It is similar in concept to BSD's TAILQ.
|
||||
*
|
||||
* Although the API is symmetrical and O(1) in both directions, due to internal
|
||||
* pointer design, it is *slightly* more efficient to insert at the end when
|
||||
* used as a queue, and to iterate forwards rather than backwards.
|
||||
*
|
||||
* Example of an entry type that can be stored to this list.
|
||||
* ~~~
|
||||
* typedef struct example_entry
|
||||
* {
|
||||
* uint8_t *data;
|
||||
* uint32_t data_count;
|
||||
* ns_list_link_t link;
|
||||
* }
|
||||
* example_entry_t;
|
||||
*
|
||||
* static NS_LIST_HEAD(example_entry_t, link) my_list;
|
||||
* ns_list_init(&my_list);
|
||||
* ~~~
|
||||
* OR
|
||||
* ~~~
|
||||
* NS_LIST_HEAD(example_entry_t, link) my_list = NS_LIST_INIT(my_list);
|
||||
* ~~~
|
||||
* OR
|
||||
* ~~~
|
||||
* static NS_LIST_DEFINE(my_list, example_entry_t, link);
|
||||
* ~~~
|
||||
* OR
|
||||
* ~~~
|
||||
* typedef NS_LIST_HEAD(example_entry_t, link) example_list_t;
|
||||
* example_list_t NS_LIST_NAME_INIT(my_list);
|
||||
* ~~~
|
||||
* NOTE: the link field SHALL NOT be accessed by the user.
|
||||
*
|
||||
* An entry can exist on multiple lists by having multiple link fields.
|
||||
*
|
||||
* All the list operations are implemented as macros, most of which are backed
|
||||
* by optionally-inline functions. The macros do not evaluate any arguments more
|
||||
* than once, unless documented.
|
||||
*
|
||||
* In macro documentation, `list_t` refers to a list type defined using
|
||||
* NS_LIST_HEAD(), and `entry_t` to the entry type that was passed to it.
|
||||
*/
|
||||
|
||||
/** \brief Underlying generic linked list head.
|
||||
*
|
||||
* Users should not use this type directly, but use the NS_LIST_HEAD() macro.
|
||||
*/
|
||||
typedef struct ns_list {
|
||||
void *first_entry; ///< Pointer to first entry, or NULL if list is empty
|
||||
void **last_nextptr; ///< Pointer to last entry's `next` pointer, or
|
||||
///< to head's `first_entry` pointer if list is empty
|
||||
} ns_list_t;
|
||||
|
||||
/** \brief Declare a list head type
|
||||
*
|
||||
* This union stores the real list head, and also encodes as compile-time type
|
||||
* information the offset of the link pointer, and the type of the entry.
|
||||
*
|
||||
* Note that type information is compiler-dependent; this means
|
||||
* ns_list_get_first() could return either `void *`, or a pointer to the actual
|
||||
* entry type. So `ns_list_get_first()->data` is not a portable construct -
|
||||
* always assign returned entry pointers to a properly typed pointer variable.
|
||||
* This assignment will be then type-checked where the compiler supports it, and
|
||||
* will dereference correctly on compilers that don't support this extension.
|
||||
* ~~~
|
||||
* NS_LIST_HEAD(example_entry_t, link) my_list;
|
||||
*
|
||||
* example_entry_t *entry = ns_list_get_first(&my_list);
|
||||
* do_something(entry->data);
|
||||
* ~~~
|
||||
* Each use of this macro generates a new anonymous union, so these two lists
|
||||
* have different types:
|
||||
* ~~~
|
||||
* NS_LIST_HEAD(example_entry_t, link) my_list1;
|
||||
* NS_LIST_HEAD(example_entry_t, link) my_list2;
|
||||
* ~~~
|
||||
* If you need to use a list type in multiple places, eg as a function
|
||||
* parameter, use typedef:
|
||||
* ~~~
|
||||
* typedef NS_LIST_HEAD(example_entry_t, link) example_list_t;
|
||||
*
|
||||
* void example_function(example_list_t *);
|
||||
* ~~~
|
||||
*/
|
||||
#define NS_LIST_HEAD(entry_type, field) \
|
||||
union \
|
||||
{ \
|
||||
ns_list_t slist; \
|
||||
NS_STATIC_ASSERT(offsetof(entry_type, field) <= UINT_FAST8_MAX, "link offset too large") \
|
||||
char (*offset)[offsetof(entry_type, field)]; \
|
||||
entry_type *type; \
|
||||
}
|
||||
|
||||
/// \privatesection
|
||||
/** \brief Get offset of link field in entry.
|
||||
* \return `(ns_list_offset_t)` The offset of the link field for entries on the specified list
|
||||
*/
|
||||
#define NS_LIST_OFFSET_(list) ((ns_list_offset_t) sizeof *(list)->offset)
|
||||
|
||||
/** \brief Get the entry type.
|
||||
* \def NS_LIST_TYPE_
|
||||
*
|
||||
* \return The type of entry on the specified list.
|
||||
*
|
||||
* Only available if the compiler provides a "typeof" operator.
|
||||
*/
|
||||
#if defined __cplusplus && __cplusplus >= 201103L
|
||||
#define NS_LIST_TYPE_(list) decltype(*(list)->type)
|
||||
#elif defined __GNUC__
|
||||
#define NS_LIST_TYPE_(list) __typeof__(*(list)->type)
|
||||
#endif
|
||||
|
||||
/** \brief Check for compatible pointer types
|
||||
*
|
||||
* Although this can be done portably, the GCC custom version is provided to
|
||||
* produce a clearer diagnostic, and it produces an error rather than a warning.
|
||||
*
|
||||
* The portable version will produce a diagnostic about a pointer mismatch on
|
||||
* the == inside the sizeof operator. For example ARM/Norcroft C gives the error:
|
||||
*
|
||||
* operand types are incompatible ("entry_t *" and "other_t *")
|
||||
*/
|
||||
#ifdef CPPCHECK
|
||||
#define NS_PTR_MATCH_(a, b, str) ((void) 0)
|
||||
#elif defined __GNUC__
|
||||
#define NS_PTR_MATCH_(a, b, str) __extension__ \
|
||||
({ NS_STATIC_ASSERT(__builtin_types_compatible_p(__typeof__ (*(a)), __typeof__ (*(b))), \
|
||||
str) })
|
||||
#else
|
||||
#define NS_PTR_MATCH_(a, b, str) ((void) sizeof ((a) == (b)))
|
||||
#endif
|
||||
|
||||
/** \brief Internal macro to cast returned entry pointers to correct type.
|
||||
*
|
||||
* Not portable in C, alas. With GCC or C++11, the "get entry" macros return
|
||||
* correctly-typed pointers. Otherwise, the macros return `void *`.
|
||||
*
|
||||
* The attempt at a portable version would work if the C `?:` operator wasn't
|
||||
* broken - `x ? (t *) : (void *)` should really have type `(t *)` in C, but
|
||||
* it has type `(void *)`, which only makes sense for C++. The `?:` is left in,
|
||||
* in case some day it works. Some compilers may still warn if this is
|
||||
* assigned to a different type.
|
||||
*/
|
||||
#ifdef NS_LIST_TYPE_
|
||||
#define NS_LIST_TYPECAST_(list, val) ((NS_LIST_TYPE_(list) *) (val))
|
||||
#else
|
||||
#define NS_LIST_TYPECAST_(list, val) (0 ? (list)->type : (val))
|
||||
#endif
|
||||
|
||||
/** \brief Internal macro to check types of input entry pointer. */
|
||||
#define NS_LIST_TYPECHECK_(list, entry) \
|
||||
(NS_PTR_MATCH_((list)->type, (entry), "incorrect entry type for list"), (entry))
|
||||
|
||||
/** \brief Type used to pass link offset to underlying functions
|
||||
*
|
||||
* We could use size_t, but it would be unnecessarily large on 8-bit systems,
|
||||
* where we can be (pretty) confident we won't have next pointers more than
|
||||
* 256 bytes into a structure.
|
||||
*/
|
||||
typedef uint_fast8_t ns_list_offset_t;
|
||||
|
||||
/// \publicsection
|
||||
/** \brief The type for the link member in the user's entry structure.
|
||||
*
|
||||
* Users should not access this member directly - just pass its name to the
|
||||
* list head macros. The funny prev pointer simplifies common operations
|
||||
* (eg insertion, removal), at the expense of complicating rare reverse iteration.
|
||||
*
|
||||
* NB - the list implementation relies on next being the first member.
|
||||
*/
|
||||
typedef struct ns_list_link {
|
||||
void *next; ///< Pointer to next entry, or NULL if none
|
||||
void **prev; ///< Pointer to previous entry's (or head's) next pointer
|
||||
} ns_list_link_t;
|
||||
|
||||
/** \brief "Poison" value placed in unattached entries' link pointers.
|
||||
* \internal What are good values for this? Platform dependent, maybe just NULL
|
||||
*/
|
||||
#define NS_LIST_POISON ((void *) 0xDEADBEEF)
|
||||
|
||||
/** \brief Initialiser for an entry's link member
|
||||
*
|
||||
* This initialiser is not required by the library, but a user may want an
|
||||
* initialiser to include in their own entry initialiser. See
|
||||
* ns_list_link_init() for more discussion.
|
||||
*/
|
||||
#define NS_LIST_LINK_INIT(name) \
|
||||
NS_FUNNY_INTPTR_OK \
|
||||
{ NS_LIST_POISON, NS_LIST_POISON } \
|
||||
NS_FUNNY_INTPTR_RESTORE
|
||||
|
||||
/** \hideinitializer \brief Initialise an entry's list link
|
||||
*
|
||||
* This "initialises" an unattached entry's link by filling the fields with
|
||||
* poison. This is optional, as unattached entries field pointers are not
|
||||
* meaningful, and it is not valid to call ns_list_get_next or similar on
|
||||
* an unattached entry.
|
||||
*
|
||||
* \param entry Pointer to an entry
|
||||
* \param field The name of the link member to initialise
|
||||
*/
|
||||
#define ns_list_link_init(entry, field) ns_list_link_init_(&(entry)->field)
|
||||
|
||||
/** \hideinitializer \brief Initialise a list
|
||||
*
|
||||
* Initialise a list head before use. A list head must be initialised using this
|
||||
* function or one of the NS_LIST_INIT()-type macros before use. A zero-initialised
|
||||
* list head is *not* valid.
|
||||
*
|
||||
* If used on a list containing existing entries, those entries will
|
||||
* become detached. (They are not modified, but their links are now effectively
|
||||
* undefined).
|
||||
*
|
||||
* \param list Pointer to a NS_LIST_HEAD() structure.
|
||||
*/
|
||||
#define ns_list_init(list) ns_list_init_(&(list)->slist)
|
||||
|
||||
/** \brief Initialiser for an empty list
|
||||
*
|
||||
* Usage in an enclosing initialiser:
|
||||
* ~~~
|
||||
* static my_type_including_list_t x = {
|
||||
* "Something",
|
||||
* 23,
|
||||
* NS_LIST_INIT(x),
|
||||
* };
|
||||
* ~~~
|
||||
* NS_LIST_DEFINE() or NS_LIST_NAME_INIT() may provide a shorter alternative
|
||||
* in simpler cases.
|
||||
*/
|
||||
#define NS_LIST_INIT(name) { { NULL, &(name).slist.first_entry } }
|
||||
|
||||
/** \brief Name and initialiser for an empty list
|
||||
*
|
||||
* Usage:
|
||||
* ~~~
|
||||
* list_t NS_LIST_NAME_INIT(foo);
|
||||
* ~~~
|
||||
* acts as
|
||||
* ~~~
|
||||
* list_t foo = { empty list };
|
||||
* ~~~
|
||||
* Also useful with designated initialisers:
|
||||
* ~~~
|
||||
* .NS_LIST_NAME_INIT(foo),
|
||||
* ~~~
|
||||
* acts as
|
||||
* ~~~
|
||||
* .foo = { empty list },
|
||||
* ~~~
|
||||
*/
|
||||
#define NS_LIST_NAME_INIT(name) name = NS_LIST_INIT(name)
|
||||
|
||||
/** \brief Define a list, and initialise to empty.
|
||||
*
|
||||
* Usage:
|
||||
* ~~~
|
||||
* static NS_LIST_DEFINE(my_list, entry_t, link);
|
||||
* ~~~
|
||||
* acts as
|
||||
* ~~~
|
||||
* static list_type my_list = { empty list };
|
||||
* ~~~
|
||||
*/
|
||||
#define NS_LIST_DEFINE(name, type, field) \
|
||||
NS_LIST_HEAD(type, field) NS_LIST_NAME_INIT(name)
|
||||
|
||||
/** \hideinitializer \brief Add an entry to the start of the linked list.
|
||||
*
|
||||
* ns_list_add_to_end() is *slightly* more efficient than ns_list_add_to_start().
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param entry `(entry_t * restrict)` Pointer to new entry to add.
|
||||
*/
|
||||
#define ns_list_add_to_start(list, entry) \
|
||||
ns_list_add_to_start_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry))
|
||||
|
||||
/** \hideinitializer \brief Add an entry to the end of the linked list.
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param entry `(entry_t * restrict)` Pointer to new entry to add.
|
||||
*/
|
||||
#define ns_list_add_to_end(list, entry) \
|
||||
ns_list_add_to_end_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry))
|
||||
|
||||
/** \hideinitializer \brief Add an entry before a specified entry.
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param before `(entry_t *)` Existing entry before which to place the new entry.
|
||||
* \param entry `(entry_t * restrict)` Pointer to new entry to add.
|
||||
*/
|
||||
#define ns_list_add_before(list, before, entry) \
|
||||
ns_list_add_before_(NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, before), NS_LIST_TYPECHECK_(list, entry))
|
||||
|
||||
/** \hideinitializer \brief Add an entry after a specified entry.
|
||||
*
|
||||
* ns_list_add_before() is *slightly* more efficient than ns_list_add_after().
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param after `(entry_t *)` Existing entry after which to place the new entry.
|
||||
* \param entry `(entry_t * restrict)` Pointer to new entry to add.
|
||||
*/
|
||||
#define ns_list_add_after(list, after, entry) \
|
||||
ns_list_add_after_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, after), NS_LIST_TYPECHECK_(list, entry))
|
||||
|
||||
/** \brief Check if a list is empty.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
*
|
||||
* \return `(bool)` true if the list is empty.
|
||||
*/
|
||||
#define ns_list_is_empty(list) ((bool) ((list)->slist.first_entry == NULL))
|
||||
|
||||
/** \brief Get the first entry.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
*
|
||||
* \return `(entry_t *)` Pointer to first entry.
|
||||
* \return NULL if list is empty.
|
||||
*/
|
||||
#define ns_list_get_first(list) NS_LIST_TYPECAST_(list, (list)->slist.first_entry)
|
||||
|
||||
/** \hideinitializer \brief Get the previous entry.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
* \param current `(const entry_t *)` Pointer to current entry.
|
||||
*
|
||||
* \return `(entry_t *)` Pointer to previous entry.
|
||||
* \return NULL if current entry is first.
|
||||
*/
|
||||
#define ns_list_get_previous(list, current) \
|
||||
NS_LIST_TYPECAST_(list, ns_list_get_previous_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current)))
|
||||
|
||||
/** \hideinitializer \brief Get the next entry.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
* \param current `(const entry_t *)` Pointer to current entry.
|
||||
*
|
||||
* \return `(entry_t *)` Pointer to next entry.
|
||||
* \return NULL if current entry is last.
|
||||
*/
|
||||
#define ns_list_get_next(list, current) \
|
||||
NS_LIST_TYPECAST_(list, ns_list_get_next_(NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current)))
|
||||
|
||||
/** \hideinitializer \brief Get the last entry.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
*
|
||||
* \return `(entry_t *)` Pointer to last entry.
|
||||
* \return NULL if list is empty.
|
||||
*/
|
||||
#define ns_list_get_last(list) \
|
||||
NS_LIST_TYPECAST_(list, ns_list_get_last_(&(list)->slist, NS_LIST_OFFSET_(list)))
|
||||
|
||||
/** \hideinitializer \brief Remove an entry.
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param entry `(entry_t *)` Entry on list to be removed.
|
||||
*/
|
||||
#define ns_list_remove(list, entry) \
|
||||
ns_list_remove_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry))
|
||||
|
||||
/** \hideinitializer \brief Replace an entry.
|
||||
*
|
||||
* \param list `(list_t *)` Pointer to list.
|
||||
* \param current `(entry_t *)` Existing entry on list to be replaced.
|
||||
* \param replacement `(entry_t * restrict)` New entry to be the replacement.
|
||||
*/
|
||||
#define ns_list_replace(list, current, replacement) \
|
||||
ns_list_replace_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current), NS_LIST_TYPECHECK_(list, replacement))
|
||||
|
||||
/** \hideinitializer \brief Concatenate two lists.
|
||||
*
|
||||
* Attach the entries on the source list to the end of the destination
|
||||
* list, leaving the source list empty.
|
||||
*
|
||||
* \param dst `(list_t *)` Pointer to destination list.
|
||||
* \param src `(list_t *)` Pointer to source list.
|
||||
*
|
||||
*/
|
||||
#define ns_list_concatenate(dst, src) \
|
||||
(NS_PTR_MATCH_(dst, src, "concatenating different list types"), \
|
||||
ns_list_concatenate_(&(dst)->slist, &(src)->slist, NS_LIST_OFFSET_(src)))
|
||||
|
||||
/** \brief Iterate forwards over a list.
|
||||
*
|
||||
* Example:
|
||||
* ~~~
|
||||
* ns_list_foreach(const my_entry_t, cur, &my_list)
|
||||
* {
|
||||
* printf("%s\n", cur->name);
|
||||
* }
|
||||
* ~~~
|
||||
* Deletion of the current entry is not permitted as its next is checked after
|
||||
* running user code.
|
||||
*
|
||||
* The iteration pointer is declared inside the loop, using C99/C++, so it
|
||||
* is not accessible after the loop. This encourages good code style, and
|
||||
* matches the semantics of C++11's "ranged for", which only provides the
|
||||
* declaration form:
|
||||
* ~~~
|
||||
* for (const my_entry_t cur : my_list)
|
||||
* ~~~
|
||||
* If you need to see the value of the iteration pointer after a `break`,
|
||||
* you will need to assign it to a variable declared outside the loop before
|
||||
* breaking:
|
||||
* ~~~
|
||||
* my_entry_t *match = NULL;
|
||||
* ns_list_foreach(my_entry_t, cur, &my_list)
|
||||
* {
|
||||
* if (cur->id == id)
|
||||
* {
|
||||
* match = cur;
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* ~~~
|
||||
*
|
||||
* The user has to specify the entry type for the pointer definition, as type
|
||||
* extraction from the list argument isn't portable. On the other hand, this
|
||||
* also permits const qualifiers, as in the example above, and serves as
|
||||
* documentation. The entry type will be checked against the list type where the
|
||||
* compiler supports it.
|
||||
*
|
||||
* \param type Entry type `([const] entry_t)`.
|
||||
* \param e Name for iteration pointer to be defined
|
||||
* inside the loop.
|
||||
* \param list `(const list_t *)` Pointer to list - evaluated multiple times.
|
||||
*/
|
||||
#define ns_list_foreach(type, e, list) \
|
||||
for (type *e = ns_list_get_first(list); e; e = ns_list_get_next(list, e))
|
||||
|
||||
/** \brief Iterate forwards over a list, where user may delete.
|
||||
*
|
||||
* As ns_list_foreach(), but deletion of current entry is permitted as its
|
||||
* next pointer is recorded before running user code.
|
||||
*
|
||||
* Example:
|
||||
* ~~~
|
||||
* ns_list_foreach_safe(my_entry_t, cur, &my_list)
|
||||
* {
|
||||
* ns_list_remove(cur);
|
||||
* }
|
||||
* ~~~
|
||||
* \param type Entry type `(entry_t)`.
|
||||
* \param e Name for iteration pointer to be defined
|
||||
* inside the loop.
|
||||
* \param list `(list_t *)` Pointer to list - evaluated multiple times.
|
||||
*/
|
||||
#define ns_list_foreach_safe(type, e, list) \
|
||||
for (type *e = ns_list_get_first(list), *_next; \
|
||||
e && (_next = ns_list_get_next(list, e), true); e = _next)
|
||||
|
||||
/** \brief Iterate backwards over a list.
|
||||
*
|
||||
* As ns_list_foreach(), but going backwards - see its documentation.
|
||||
* Iterating forwards is *slightly* more efficient.
|
||||
*/
|
||||
#define ns_list_foreach_reverse(type, e, list) \
|
||||
for (type *e = ns_list_get_last(list); e; e = ns_list_get_previous(list, e))
|
||||
|
||||
/** \brief Iterate backwards over a list, where user may delete.
|
||||
*
|
||||
* As ns_list_foreach_safe(), but going backwards - see its documentation.
|
||||
* Iterating forwards is *slightly* more efficient.
|
||||
*/
|
||||
#define ns_list_foreach_reverse_safe(type, e, list) \
|
||||
for (type *e = ns_list_get_last(list), *_next; \
|
||||
e && (_next = ns_list_get_previous(list, e), true); e = _next)
|
||||
|
||||
/** \hideinitializer \brief Count entries on a list
|
||||
*
|
||||
* Unlike other operations, this is O(n). Note: if list might contain over
|
||||
* 65535 entries, this function **must not** be used to get the entry count.
|
||||
*
|
||||
* \param list `(const list_t *)` Pointer to list.
|
||||
|
||||
* \return `(uint_fast16_t)` Number of entries that are stored in list.
|
||||
*/
|
||||
#define ns_list_count(list) ns_list_count_(&(list)->slist, NS_LIST_OFFSET_(list))
|
||||
|
||||
/** \privatesection
|
||||
* Internal functions - designed to be accessed using corresponding macros above
|
||||
*/
|
||||
NS_INLINE void ns_list_init_(ns_list_t *list);
|
||||
NS_INLINE void ns_list_link_init_(ns_list_link_t *link);
|
||||
NS_INLINE void ns_list_add_to_start_(ns_list_t *list, ns_list_offset_t link_offset, void *restrict entry);
|
||||
NS_INLINE void ns_list_add_to_end_(ns_list_t *list, ns_list_offset_t link_offset, void *restrict entry);
|
||||
NS_INLINE void ns_list_add_before_(ns_list_offset_t link_offset, void *before, void *restrict entry);
|
||||
NS_INLINE void ns_list_add_after_(ns_list_t *list, ns_list_offset_t link_offset, void *after, void *restrict entry);
|
||||
NS_INLINE void *ns_list_get_next_(ns_list_offset_t link_offset, const void *current);
|
||||
NS_INLINE void *ns_list_get_previous_(const ns_list_t *list, ns_list_offset_t link_offset, const void *current);
|
||||
NS_INLINE void *ns_list_get_last_(const ns_list_t *list, ns_list_offset_t offset);
|
||||
NS_INLINE void ns_list_remove_(ns_list_t *list, ns_list_offset_t link_offset, void *entry);
|
||||
NS_INLINE void ns_list_replace_(ns_list_t *list, ns_list_offset_t link_offset, void *current, void *restrict replacement);
|
||||
NS_INLINE void ns_list_concatenate_(ns_list_t *dst, ns_list_t *src, ns_list_offset_t offset);
|
||||
NS_INLINE uint_fast16_t ns_list_count_(const ns_list_t *list, ns_list_offset_t link_offset);
|
||||
|
||||
/* Provide definitions, either for inlining, or for ns_list.c */
|
||||
#if defined NS_ALLOW_INLINING || defined NS_LIST_FN
|
||||
#ifndef NS_LIST_FN
|
||||
#define NS_LIST_FN NS_INLINE
|
||||
#endif
|
||||
|
||||
/* Pointer to the link member in entry e */
|
||||
#define NS_LIST_LINK_(e, offset) ((ns_list_link_t *)((char *)(e) + offset))
|
||||
|
||||
/* Lvalue of the next link pointer in entry e */
|
||||
#define NS_LIST_NEXT_(e, offset) (NS_LIST_LINK_(e, offset)->next)
|
||||
|
||||
/* Lvalue of the prev link pointer in entry e */
|
||||
#define NS_LIST_PREV_(e, offset) (NS_LIST_LINK_(e, offset)->prev)
|
||||
|
||||
/* Convert a pointer to a link member back to the entry;
|
||||
* works for linkptr either being a ns_list_link_t pointer, or its next pointer,
|
||||
* as the next pointer is first in the ns_list_link_t */
|
||||
#define NS_LIST_ENTRY_(linkptr, offset) ((void *)((char *)(linkptr) - offset))
|
||||
|
||||
NS_LIST_FN void ns_list_init_(ns_list_t *list)
|
||||
{
|
||||
list->first_entry = NULL;
|
||||
list->last_nextptr = &list->first_entry;
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_link_init_(ns_list_link_t *link)
|
||||
{
|
||||
NS_FUNNY_INTPTR_OK
|
||||
link->next = NS_LIST_POISON;
|
||||
link->prev = NS_LIST_POISON;
|
||||
NS_FUNNY_INTPTR_RESTORE
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_add_to_start_(ns_list_t *list, ns_list_offset_t offset, void *restrict entry)
|
||||
{
|
||||
void *next;
|
||||
|
||||
NS_LIST_PREV_(entry, offset) = &list->first_entry;
|
||||
NS_LIST_NEXT_(entry, offset) = next = list->first_entry;
|
||||
|
||||
if (next) {
|
||||
NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(entry, offset);
|
||||
} else {
|
||||
list->last_nextptr = &NS_LIST_NEXT_(entry, offset);
|
||||
}
|
||||
|
||||
list->first_entry = entry;
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_add_after_(ns_list_t *list, ns_list_offset_t offset, void *current, void *restrict entry)
|
||||
{
|
||||
void *next;
|
||||
|
||||
NS_LIST_PREV_(entry, offset) = &NS_LIST_NEXT_(current, offset);
|
||||
NS_LIST_NEXT_(entry, offset) = next = NS_LIST_NEXT_(current, offset);
|
||||
|
||||
if (next) {
|
||||
NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(entry, offset);
|
||||
} else {
|
||||
list->last_nextptr = &NS_LIST_NEXT_(entry, offset);
|
||||
}
|
||||
|
||||
NS_LIST_NEXT_(current, offset) = entry;
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_add_before_(ns_list_offset_t offset, void *current, void *restrict entry)
|
||||
{
|
||||
void **prev_nextptr;
|
||||
|
||||
NS_LIST_NEXT_(entry, offset) = current;
|
||||
NS_LIST_PREV_(entry, offset) = prev_nextptr = NS_LIST_PREV_(current, offset);
|
||||
*prev_nextptr = entry;
|
||||
NS_LIST_PREV_(current, offset) = &NS_LIST_NEXT_(entry, offset);
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_add_to_end_(ns_list_t *list, ns_list_offset_t offset, void *restrict entry)
|
||||
{
|
||||
void **prev_nextptr;
|
||||
|
||||
NS_LIST_NEXT_(entry, offset) = NULL;
|
||||
NS_LIST_PREV_(entry, offset) = prev_nextptr = list->last_nextptr;
|
||||
*prev_nextptr = entry;
|
||||
list->last_nextptr = &NS_LIST_NEXT_(entry, offset);
|
||||
}
|
||||
|
||||
NS_LIST_FN void *ns_list_get_next_(ns_list_offset_t offset, const void *current)
|
||||
{
|
||||
return NS_LIST_NEXT_(current, offset);
|
||||
}
|
||||
|
||||
NS_LIST_FN void *ns_list_get_previous_(const ns_list_t *list, ns_list_offset_t offset, const void *current)
|
||||
{
|
||||
if (current == list->first_entry) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Tricky. We don't have a direct previous pointer, but a pointer to the
|
||||
// pointer that points to us - ie &head->first_entry OR &{prev}->next.
|
||||
// This makes life easier on insertion and removal, but this is where we
|
||||
// pay the price.
|
||||
|
||||
// We have to check manually for being the first entry above, so we know it's
|
||||
// a real link's next pointer. Then next is the first field of
|
||||
// ns_list_link_t, so we can use the normal offset value.
|
||||
|
||||
return NS_LIST_ENTRY_(NS_LIST_PREV_(current, offset), offset);
|
||||
}
|
||||
|
||||
NS_LIST_FN void *ns_list_get_last_(const ns_list_t *list, ns_list_offset_t offset)
|
||||
{
|
||||
if (!list->first_entry) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// See comments in ns_list_get_previous_()
|
||||
return NS_LIST_ENTRY_(list->last_nextptr, offset);
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_remove_(ns_list_t *list, ns_list_offset_t offset, void *removed)
|
||||
{
|
||||
void *next;
|
||||
void **prev_nextptr;
|
||||
|
||||
next = NS_LIST_NEXT_(removed, offset);
|
||||
prev_nextptr = NS_LIST_PREV_(removed, offset);
|
||||
if (next) {
|
||||
NS_LIST_PREV_(next, offset) = prev_nextptr;
|
||||
} else {
|
||||
list->last_nextptr = prev_nextptr;
|
||||
}
|
||||
*prev_nextptr = next;
|
||||
|
||||
ns_list_link_init_(NS_LIST_LINK_(removed, offset));
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_replace_(ns_list_t *list, ns_list_offset_t offset, void *current, void *restrict replacement)
|
||||
{
|
||||
void *next;
|
||||
void **prev_nextptr;
|
||||
|
||||
NS_LIST_PREV_(replacement, offset) = prev_nextptr = NS_LIST_PREV_(current, offset);
|
||||
NS_LIST_NEXT_(replacement, offset) = next = NS_LIST_NEXT_(current, offset);
|
||||
|
||||
if (next) {
|
||||
NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(replacement, offset);
|
||||
} else {
|
||||
list->last_nextptr = &NS_LIST_NEXT_(replacement, offset);
|
||||
}
|
||||
*prev_nextptr = replacement;
|
||||
|
||||
ns_list_link_init_(NS_LIST_LINK_(current, offset));
|
||||
}
|
||||
|
||||
NS_LIST_FN void ns_list_concatenate_(ns_list_t *dst, ns_list_t *src, ns_list_offset_t offset)
|
||||
{
|
||||
ns_list_link_t *src_first;
|
||||
|
||||
src_first = src->first_entry;
|
||||
if (!src_first) {
|
||||
return;
|
||||
}
|
||||
|
||||
*dst->last_nextptr = src_first;
|
||||
NS_LIST_PREV_(src_first, offset) = dst->last_nextptr;
|
||||
dst->last_nextptr = src->last_nextptr;
|
||||
|
||||
ns_list_init_(src);
|
||||
}
|
||||
|
||||
NS_LIST_FN uint_fast16_t ns_list_count_(const ns_list_t *list, ns_list_offset_t offset)
|
||||
{
|
||||
uint_fast16_t count = 0;
|
||||
|
||||
for (void *p = list->first_entry; p; p = NS_LIST_NEXT_(p, offset)) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
#endif /* defined NS_ALLOW_INLINING || defined NS_LIST_FN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NS_LIST_H_ */
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* Copyright (c) 2014-2015 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.
|
||||
*/
|
||||
/*
|
||||
* ns_types.h - Basic compiler and type setup for Nanostack libraries.
|
||||
*/
|
||||
#ifndef NS_TYPES_H_
|
||||
#define NS_TYPES_H_
|
||||
|
||||
/** \file
|
||||
* \brief Basic compiler and type setup
|
||||
*
|
||||
* We currently assume C99 or later.
|
||||
*
|
||||
* C99 features being relied on:
|
||||
*
|
||||
* - <inttypes.h> and <stdbool.h>
|
||||
* - inline (with C99 semantics, not C++ as per default GCC);
|
||||
* - designated initialisers;
|
||||
* - compound literals;
|
||||
* - restrict;
|
||||
* - [static N] in array parameters;
|
||||
* - declarations in for statements;
|
||||
* - mixing declarations and statements
|
||||
*
|
||||
* Compilers should be set to C99 or later mode when building Nanomesh source.
|
||||
* For GCC this means "-std=gnu99" (C99 with usual GNU extensions).
|
||||
*
|
||||
* Also, a little extra care is required for public header files that could be
|
||||
* included from C++, especially as C++ lacks some C99 features.
|
||||
*
|
||||
* (TODO: as this is exposed to API users, do we need a predefine to distinguish
|
||||
* internal and external use, for finer control? Not yet, but maybe...)
|
||||
*/
|
||||
|
||||
/* Make sure <stdint.h> defines its macros if C++ */
|
||||
#ifndef __STDC_LIMIT_MACROS
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#endif
|
||||
#ifndef __STDC_CONSTANT_MACROS
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h> // includes <stdint.h>; debugf() users need PRIu32 etc
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* Create the optional <stdint.h> 24-bit types if they don't exist (worth trying
|
||||
* to use them, as they could exist and be more efficient than 32-bit on 8-bit
|
||||
* systems...)
|
||||
*/
|
||||
#ifndef UINT24_LEAST_MAX
|
||||
typedef uint_least32_t uint_least24_t;
|
||||
#define UINT_LEAST24_MAX UINT_LEAST32_MAX
|
||||
#define UINT24_C(x) UINT32_C(x)
|
||||
#define PRIoLEAST24 PRIoLEAST32
|
||||
#define PRIuLEAST24 PRIuLEAST32
|
||||
#define PRIxLEAST24 PRIxLEAST32
|
||||
#define PRIXLEAST24 PRIXLEAST32
|
||||
#endif
|
||||
|
||||
#ifndef INT24_LEAST_MAX
|
||||
typedef int_least32_t int_least24_t;
|
||||
#define INT24_LEAST_MIN INT_LEAST32_MIN
|
||||
#define INT24_LEAST_MAX INT_LEAST32_MAX
|
||||
#define INT24_C(x) INT32_C(x)
|
||||
#define PRIdLEAST24 PRIdLEAST32
|
||||
#define PRIiLEAST24 PRIiLEAST32
|
||||
#endif
|
||||
|
||||
#ifndef UINT24_FAST_MAX
|
||||
typedef uint_fast32_t uint_fast24_t;
|
||||
#define UINT_FAST24_MAX UINT_FAST32_MAX
|
||||
#define PRIoFAST24 PRIoFAST32
|
||||
#define PRIuFAST24 PRIuFAST32
|
||||
#define PRIxFAST24 PRIxFAST32
|
||||
#define PRIXFAST24 PRIXFAST32
|
||||
#endif
|
||||
|
||||
#ifndef INT24_FAST_MAX
|
||||
typedef int_fast32_t int_fast24_t;
|
||||
#define INT_FAST24_MIN INT_FAST32_MIN
|
||||
#define INT_FAST24_MAX INT_FAST32_MAX
|
||||
#define PRIdFAST24 PRIdFAST32
|
||||
#define PRIiFAST24 PRIiFAST32
|
||||
#endif
|
||||
|
||||
/* Function attribute - C11 "noreturn" or C++11 "[[noreturn]]" */
|
||||
#ifndef NS_NORETURN
|
||||
#if defined __cplusplus && __cplusplus >= 201103L
|
||||
#define NS_NORETURN [[noreturn]]
|
||||
#elif !defined __cplusplus && __STDC_VERSION__ >= 201112L
|
||||
#define NS_NORETURN _Noreturn
|
||||
#elif defined __GNUC__
|
||||
#define NS_NORETURN __attribute__((__noreturn__))
|
||||
#elif defined __CC_ARM
|
||||
#define NS_NORETURN __declspec(noreturn)
|
||||
#elif defined __IAR_SYSTEMS_ICC__
|
||||
#define NS_NORETURN __noreturn
|
||||
#else
|
||||
#define NS_NORETURN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* C11's "alignas" macro, emulated for integer expressions if necessary */
|
||||
#ifndef __alignas_is_defined
|
||||
#if defined __CC_ARM || defined __TASKING__
|
||||
#define alignas(n) __align(n)
|
||||
#define __alignas_is_defined 1
|
||||
#elif (__STDC_VERSION__ >= 201112L) || (defined __cplusplus && __cplusplus >= 201103L)
|
||||
#include <stdalign.h>
|
||||
#elif defined __GNUC__
|
||||
#define alignas(n) __attribute__((__aligned__(n)))
|
||||
#define __alignas_is_defined 1
|
||||
#elif defined __IAR_SYSTEMS_ICC__
|
||||
/* Does this really just apply to the next variable? */
|
||||
#define alignas(n) __Alignas(data_alignment=n)
|
||||
#define __Alignas(x) _Pragma(#x)
|
||||
#define __alignas_is_defined 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Marker for functions or objects that may be unused, suppressing warnings.
|
||||
* Place after the identifier:
|
||||
* ~~~
|
||||
* static int X MAYBE_UNUSED = 3;
|
||||
* static int foo(void) MAYBE_UNUSED;
|
||||
* ~~~
|
||||
*/
|
||||
#if defined __CC_ARM || defined __GNUC__
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define MAYBE_UNUSED
|
||||
#endif
|
||||
|
||||
/*
|
||||
* C++ (even C++11) doesn't provide restrict: define away or provide
|
||||
* alternative.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
#ifdef __GNUC__
|
||||
#define restrict __restrict
|
||||
#else
|
||||
#define restrict
|
||||
#endif
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
/**
|
||||
* C++ doesn't allow "static" in function parameter types: ie
|
||||
* ~~~
|
||||
* entry_t *find_entry(const uint8_t address[static 16])
|
||||
* ~~~
|
||||
* If a header file may be included from C++, use this __static define instead.
|
||||
*
|
||||
* (Syntax introduced in C99 - `uint8_t address[16]` in a prototype was always
|
||||
* equivalent to `uint8_t *address`, but the C99 addition of static tells the
|
||||
* compiler that address is never NULL, and always points to at least 16
|
||||
* elements. This adds no new type-checking, but the information could aid
|
||||
* compiler optimisation, and it can serve as documentation).
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
#define __static
|
||||
#else
|
||||
#define __static static
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define NS_GCC_VERSION (__GNUC__ * 10000 \
|
||||
+ __GNUC_MINOR__ * 100 \
|
||||
+ __GNUC_PATCHLEVEL__)
|
||||
#endif
|
||||
|
||||
/** \brief Compile-time assertion
|
||||
*
|
||||
* C11 provides _Static_assert, as does GCC even in C99 mode (and
|
||||
* as a freestanding implementation, we can't rely on <assert.h> to get
|
||||
* the static_assert macro).
|
||||
* C++11 provides static_assert as a keyword, as does G++ in C++0x mode.
|
||||
*
|
||||
* The assertion acts as a declaration that can be placed at file scope, in a
|
||||
* code block (except after a label), or as a member of a struct/union. It
|
||||
* produces a compiler error if "test" evaluates to 0.
|
||||
*
|
||||
* Note that this *includes* the required semicolon when defined, else it
|
||||
* is totally empty, permitting use in structs. (If the user provided the `;`,
|
||||
* it would leave an illegal stray `;` if unavailable).
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
# if __cplusplus >= 201103L || __cpp_static_assert >= 200410
|
||||
# define NS_STATIC_ASSERT(test, str) static_assert(test, str);
|
||||
# elif defined __GXX_EXPERIMENTAL_CXX0X__ && NS_GCC_VERSION >= 40300
|
||||
# define NS_STATIC_ASSERT(test, str) __extension__ static_assert(test, str);
|
||||
# else
|
||||
# define NS_STATIC_ASSERT(test, str)
|
||||
# endif
|
||||
#else /* C */
|
||||
# if __STDC_VERSION__ >= 201112L
|
||||
# define NS_STATIC_ASSERT(test, str) _Static_assert(test, str);
|
||||
# elif defined __GNUC__ && NS_GCC_VERSION >= 40600 && !defined __CC_ARM
|
||||
# ifdef _Static_assert
|
||||
/*
|
||||
* Some versions of glibc cdefs.h (which comes in via <stdint.h> above)
|
||||
* attempt to define their own _Static_assert (if GCC < 4.6 or
|
||||
* __STRICT_ANSI__) using an extern declaration, which doesn't work in a
|
||||
* struct/union.
|
||||
*
|
||||
* For GCC >= 4.6 and __STRICT_ANSI__, we can do better - just use
|
||||
* the built-in _Static_assert with __extension__. We have to do this, as
|
||||
* ns_list.h needs to use it in a union. No way to get at it though, without
|
||||
* overriding their define.
|
||||
*/
|
||||
# undef _Static_assert
|
||||
# define _Static_assert(x, y) __extension__ _Static_assert(x, y)
|
||||
# endif
|
||||
# define NS_STATIC_ASSERT(test, str) __extension__ _Static_assert(test, str);
|
||||
# else
|
||||
# define NS_STATIC_ASSERT(test, str)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** \brief Pragma to suppress warnings about unusual pointer values.
|
||||
*
|
||||
* Useful if using "poison" values.
|
||||
*/
|
||||
#ifdef __IAR_SYSTEMS_ICC__
|
||||
#define NS_FUNNY_INTPTR_OK _Pragma("diag_suppress=Pe1053")
|
||||
#define NS_FUNNY_INTPTR_RESTORE _Pragma("diag_default=Pe1053")
|
||||
#else
|
||||
#define NS_FUNNY_INTPTR_OK
|
||||
#define NS_FUNNY_INTPTR_RESTORE
|
||||
#endif
|
||||
|
||||
/** \brief Convert pointer to member to pointer to containing structure */
|
||||
#define NS_CONTAINER_OF(ptr, type, member) \
|
||||
((type *) ((char *) (ptr) - offsetof(type, member)))
|
||||
|
||||
/*
|
||||
* Inlining could cause problems when mixing with C++; provide a mechanism to
|
||||
* disable it. This could also be turned off for other reasons (although
|
||||
* this can usually be done through a compiler flag, eg -O0 on gcc).
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
#define NS_ALLOW_INLINING
|
||||
#endif
|
||||
|
||||
/* There is inlining problem in GCC version 4.1.x and we know it works in 4.6.3 */
|
||||
#if defined __GNUC__ && NS_GCC_VERSION < 40600
|
||||
#undef NS_ALLOW_INLINING
|
||||
#endif
|
||||
|
||||
/** \brief Mark a potentially-inlineable function.
|
||||
*
|
||||
* We follow C99 semantics, which requires precisely one external definition.
|
||||
* To also allow inlining to be totally bypassed under control of
|
||||
* NS_ALLOW_INLINING, code can be structured as per the example of ns_list:
|
||||
*
|
||||
* foo.h
|
||||
* -----
|
||||
* ~~~
|
||||
* NS_INLINE int my_func(int);
|
||||
*
|
||||
* #if defined NS_ALLOW_INLINING || defined FOO_FN
|
||||
* #ifndef FOO_FN
|
||||
* #define FOO_FN NS_INLINE
|
||||
* #endif
|
||||
* FOO_FN int my_func(int a)
|
||||
* {
|
||||
* definition;
|
||||
* }
|
||||
* #endif
|
||||
* ~~~
|
||||
* foo.c
|
||||
* -----
|
||||
* ~~~
|
||||
* #define FOO_FN extern
|
||||
* #include "foo.h"
|
||||
* ~~~
|
||||
* Which generates:
|
||||
* ~~~
|
||||
* NS_ALLOW_INLINING set NS_ALLOW_INLINING unset
|
||||
* ===================== =======================
|
||||
* Include foo.h Include foo.h
|
||||
* ------------- -------------
|
||||
* inline int my_func(int); int my_func(int);
|
||||
*
|
||||
* // inline definition
|
||||
* inline int my_func(int a)
|
||||
* {
|
||||
* definition;
|
||||
* }
|
||||
*
|
||||
* Compile foo.c Compile foo.c
|
||||
* ------------- -------------
|
||||
* (from .h) inline int my_func(int); int my_func(int);
|
||||
*
|
||||
* // external definition
|
||||
* // because of no "inline" // normal external definition
|
||||
* extern int my_func(int a) extern int my_func(int a)
|
||||
* { {
|
||||
* definition; definition;
|
||||
* } }
|
||||
* ~~~
|
||||
*
|
||||
* Note that even with inline keywords, whether the compiler inlines or not is
|
||||
* up to it. For example, gcc at "-O0" will not inline at all, and will always
|
||||
* call the real functions in foo.o, just as if NS_ALLOW_INLINING was unset.
|
||||
* At "-O2", gcc could potentially inline everything, meaning that foo.o is not
|
||||
* referenced at all.
|
||||
*
|
||||
* Alternatively, you could use "static inline", which gives every caller its
|
||||
* own internal definition. This is compatible with C++ inlining (which expects
|
||||
* the linker to eliminate duplicates), but in C it's less efficient if the code
|
||||
* ends up non-inlined, and it's harder to breakpoint. I don't recommend it
|
||||
* except for the most trivial functions (which could then probably be macros).
|
||||
*/
|
||||
#ifdef NS_ALLOW_INLINING
|
||||
#define NS_INLINE inline
|
||||
#else
|
||||
#define NS_INLINE
|
||||
#endif
|
||||
|
||||
#if defined __SDCC_mcs51 || defined __ICC8051__ || defined __C51__
|
||||
|
||||
/* The 8051 environments: SDCC (historic), IAR (current), Keil (future?) */
|
||||
|
||||
#define NS_LARGE __xdata
|
||||
#define NS_LARGE_PTR __xdata
|
||||
#ifdef __ICC8051__
|
||||
#define NS_REENTRANT
|
||||
#define NS_REENTRANT_PREFIX __idata_reentrant
|
||||
#else
|
||||
#define NS_REENTRANT __reentrant
|
||||
#define NS_REENTRANT_PREFIX
|
||||
#endif
|
||||
#define NS_NEAR_FUNC __near_func
|
||||
|
||||
#else
|
||||
|
||||
/* "Normal" systems. Define it all away. */
|
||||
#define NS_LARGE
|
||||
#define NS_LARGE_PTR
|
||||
#define NS_REENTRANT
|
||||
#define NS_REENTRANT_PREFIX
|
||||
#define NS_NEAR_FUNC
|
||||
|
||||
#endif
|
||||
|
||||
/** \brief Scatter-gather descriptor
|
||||
*
|
||||
* Slightly optimised for small platforms - we assume we won't need any
|
||||
* element bigger than 64K.
|
||||
*/
|
||||
typedef struct ns_iovec {
|
||||
void *iov_base;
|
||||
uint_fast16_t iov_len;
|
||||
} ns_iovec_t;
|
||||
|
||||
#endif /* NS_TYPES_H */
|
|
@ -0,0 +1,27 @@
|
|||
# only build tests on targets that declare they are like posix
|
||||
if(DEFINED TARGET_LIKE_X86_WINDOWS_NATIVE OR DEFINED TARGET_LIKE_X86_LINUX_NATIVE)
|
||||
|
||||
# describe the test executable
|
||||
add_executable(mbed_client_cli_test Test.cpp)
|
||||
|
||||
include_directories("../yotta_modules/cpputest")
|
||||
|
||||
# describe what the test executable needs to link with
|
||||
target_link_libraries(mbed_client_cli_test
|
||||
"mbed-client-cli"
|
||||
"mbed-trace"
|
||||
cpputest
|
||||
)
|
||||
add_definitions("-Wno-write-strings")
|
||||
|
||||
# describe what is actual test binary
|
||||
if(DEFINED TARGET_LIKE_X86_WINDOWS_NATIVE)
|
||||
SET(TEST_EXECUTABLE "build/x86-windows-native/test/mbed_client_cli_test")
|
||||
endif()
|
||||
if(DEFINED TARGET_LIKE_X86_LINUX_NATIVE)
|
||||
SET(TEST_EXECUTABLE "../../../build/x86-linux-native/test/mbed_client_cli_test")
|
||||
endif()
|
||||
|
||||
add_test(mbed_client_cli_test ${TEST_EXECUTABLE})
|
||||
|
||||
endif()
|
|
@ -0,0 +1,785 @@
|
|||
/*
|
||||
* 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 \test\Test.c
|
||||
*
|
||||
* \brief Unit tests for mbed-client-cli
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
#include "mbed-cpputest/CppUTest/TestHarness.h"
|
||||
#include "mbed-cpputest/CppUTest/SimpleString.h"
|
||||
#include "mbed-cpputest/CppUTest/CommandLineTestRunner.h"
|
||||
|
||||
#define YOTTA_CFG_MBED_TRACE 1
|
||||
#define YOTTA_CFG_MBED_TRACE_FEA_IPV6 0
|
||||
#include "mbed-trace/mbed_trace.h"
|
||||
#include "mbed-client-cli/ns_cmdline.h"
|
||||
#define MAX(x,y) (x>y?x:y)
|
||||
#define ARRAY_CMP(x, y) \
|
||||
MEMCMP_EQUAL(x, y, MAX(strlen(x), strlen(y)))
|
||||
|
||||
int main(int ac, char **av)
|
||||
{
|
||||
return CommandLineTestRunner::RunAllTests(ac, av);
|
||||
}
|
||||
|
||||
#define BUFSIZE 1024
|
||||
char buf[BUFSIZE] = {0};
|
||||
#define INIT_BUF() memset(buf, 0, BUFSIZE)
|
||||
int cmd_dummy(int argc, char *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mutex_wait_count = 0;
|
||||
int mutex_release_count = 0;
|
||||
int mutex_count_expected_difference = 1;
|
||||
bool check_mutex_lock_state = false;
|
||||
void my_mutex_wait()
|
||||
{
|
||||
mutex_wait_count++;
|
||||
}
|
||||
void my_mutex_release()
|
||||
{
|
||||
mutex_release_count++;
|
||||
}
|
||||
|
||||
void myprint(const char *fmt, va_list ap)
|
||||
{
|
||||
if (check_mutex_lock_state) {
|
||||
CHECK((mutex_wait_count - mutex_release_count) == mutex_count_expected_difference);
|
||||
}
|
||||
vsnprintf(buf + strlen(buf), BUFSIZE - strlen(buf), fmt, ap);
|
||||
//printf("\nMYPRINT: %s\n", buf); //for test test
|
||||
}
|
||||
void input(const char *str)
|
||||
{
|
||||
while (*str != 0) {
|
||||
cmd_char_input(*str++);
|
||||
}
|
||||
}
|
||||
|
||||
#define REQUEST(x) input(x);INIT_BUF();cmd_char_input('\r');
|
||||
#define RESPONSE(x) "\r\n" x "\r\n\r\x1B[2K/> \x1B[1D"
|
||||
#define CMDLINE(x) "\r\x1b[2K/>" x "\x1b[1D"
|
||||
|
||||
#define FORWARD "C"
|
||||
#define BACKWARD "D"
|
||||
#define CMDLINE_CUR(x, cursor, dir) "\r\x1b[2K/>" x "\x1b[" cursor "" dir
|
||||
#define CLEAN() cmd_char_input('\r');INIT_BUF();
|
||||
|
||||
//vt100 keycodes
|
||||
#define HOME() input("\x1b[1~")
|
||||
#define INSERT() input("\x1b[2~")
|
||||
#define DELETE() input("\x1b[3~")
|
||||
#define BACKSPACE() input("\x7f")
|
||||
#define LEFT() input("\x1b[D")
|
||||
#define LEFT_N(n) for(int i=0;i<n;i++) LEFT();
|
||||
#define RIGHT() input("\x1b[C")
|
||||
#define RIGHT_N(n) for(int i=0;i<n;i++) RIGHT()
|
||||
#define UP() input("\x1b[A")
|
||||
#define DOWN() input("\x1b[B")
|
||||
#define ESC() input("\x03")
|
||||
#define PAGE_DOWN() input("\x1b[6~")
|
||||
#define PAGE_UP() input("\x1b[5~")
|
||||
|
||||
void cmd_ready_cb(int retcode)
|
||||
{
|
||||
cmd_next(retcode);
|
||||
}
|
||||
|
||||
TEST_GROUP(cli)
|
||||
{
|
||||
void setup()
|
||||
{
|
||||
cmd_init(&myprint);
|
||||
cmd_set_ready_cb(cmd_ready_cb);
|
||||
INIT_BUF();
|
||||
}
|
||||
void teardown()
|
||||
{
|
||||
INIT_BUF();
|
||||
cmd_free();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(cli, init)
|
||||
{
|
||||
}
|
||||
TEST(cli, cmd_printf_with_mutex_not_set)
|
||||
{
|
||||
cmd_mutex_wait_func(0);
|
||||
cmd_mutex_release_func(0);
|
||||
int mutex_call_count_at_entry = mutex_wait_count;
|
||||
check_mutex_lock_state = false;
|
||||
|
||||
cmd_printf("Hello hello!");
|
||||
STRCMP_EQUAL("Hello hello!" , buf);
|
||||
|
||||
CHECK(mutex_call_count_at_entry == mutex_wait_count);
|
||||
CHECK(mutex_call_count_at_entry == mutex_release_count);
|
||||
|
||||
cmd_mutex_wait_func(my_mutex_wait);
|
||||
cmd_mutex_release_func(my_mutex_release);
|
||||
}
|
||||
TEST(cli, cmd_printf_with_mutex_set)
|
||||
{
|
||||
cmd_mutex_wait_func(my_mutex_wait);
|
||||
cmd_mutex_release_func(my_mutex_release);
|
||||
check_mutex_lock_state = true;
|
||||
|
||||
cmd_printf("!olleh olleH");
|
||||
STRCMP_EQUAL("!olleh olleH" , buf);
|
||||
CHECK(mutex_wait_count == mutex_release_count);
|
||||
|
||||
check_mutex_lock_state = false;
|
||||
cmd_mutex_wait_func(0);
|
||||
cmd_mutex_release_func(0);
|
||||
}
|
||||
TEST(cli, external_mutex_handles)
|
||||
{
|
||||
cmd_mutex_wait_func(my_mutex_wait);
|
||||
cmd_mutex_release_func(my_mutex_release);
|
||||
check_mutex_lock_state = true;
|
||||
mutex_count_expected_difference = 2;
|
||||
|
||||
cmd_mutex_lock();
|
||||
cmd_printf("!olleh olleH");
|
||||
STRCMP_EQUAL("!olleh olleH" , buf);
|
||||
cmd_mutex_unlock();
|
||||
CHECK(mutex_wait_count == mutex_release_count);
|
||||
|
||||
mutex_count_expected_difference = 1;
|
||||
check_mutex_lock_state = false;
|
||||
cmd_mutex_wait_func(0);
|
||||
cmd_mutex_release_func(0);
|
||||
}
|
||||
TEST(cli, parameters_index)
|
||||
{
|
||||
char *argv[] = { "cmd", "p1", "p2", "p3", "p4", "p5" };
|
||||
int idx = cmd_parameter_index(6, argv, "p4");
|
||||
CHECK_EQUAL(4, idx);
|
||||
|
||||
idx = cmd_parameter_index(6, argv, "p6");
|
||||
CHECK_EQUAL(-1, idx);
|
||||
|
||||
idx = cmd_parameter_index(6, argv, "p1");
|
||||
CHECK_EQUAL(1, idx);
|
||||
}
|
||||
|
||||
TEST(cli, parameters_bools)
|
||||
{
|
||||
char *argv[] = { "cmd", "p1", "-p2", "false", "p4", "p5" };
|
||||
char *argv2[] = { "cmd", "p1", "-p2", "true", "p4", "p5" };
|
||||
|
||||
bool on, ok;
|
||||
ok = cmd_parameter_bool(6, argv, "-p2", &on);
|
||||
CHECK_EQUAL(true, ok);
|
||||
CHECK_EQUAL(false, on);
|
||||
|
||||
ok = cmd_parameter_bool(6, argv2, "-p2", &on);
|
||||
CHECK_EQUAL(true, ok);
|
||||
CHECK_EQUAL(true, on);
|
||||
|
||||
ok = cmd_parameter_bool(6, argv2, "p5", &on);
|
||||
CHECK_EQUAL(false, ok);
|
||||
}
|
||||
TEST(cli, parameters_val)
|
||||
{
|
||||
bool ok;
|
||||
char *val;
|
||||
char *argv[] = { "cmd", "p1", "p2", "p3", "p4", "p5" };
|
||||
|
||||
ok = cmd_parameter_val(6, argv, "p2", &val);
|
||||
CHECK_EQUAL(true, ok);
|
||||
ARRAY_CMP("p3", val);
|
||||
|
||||
ok = cmd_parameter_val(6, argv, "p3", &val);
|
||||
CHECK_EQUAL(true, ok);
|
||||
ARRAY_CMP("p4", val);
|
||||
|
||||
ok = cmd_parameter_val(6, argv, "p5", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
}
|
||||
|
||||
TEST(cli, parameters_int)
|
||||
{
|
||||
bool ok;
|
||||
int val;
|
||||
char *argv[] = { "cmd", "p1", "p2", "3", "p4", "555fail", "p5" };
|
||||
|
||||
ok = cmd_parameter_int(6, argv, "p2", &val);
|
||||
CHECK_EQUAL(true, ok);
|
||||
CHECK_EQUAL(3, val);
|
||||
|
||||
ok = cmd_parameter_int(6, argv, "p1", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
|
||||
ok = cmd_parameter_int(6, argv, "p4", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
|
||||
ok = cmd_parameter_int(6, argv, "p5", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
}
|
||||
TEST(cli, parameters_float)
|
||||
{
|
||||
bool ok;
|
||||
float val;
|
||||
float val2 = 3.14159;
|
||||
char *argv[] = { "cmd", "p1", "3.14159", "p3", "3.14159 ", "p4", "3.14fail ", "p5" };
|
||||
|
||||
ok = cmd_parameter_float(8, argv, "p1", &val);
|
||||
CHECK_EQUAL(true, ok);
|
||||
CHECK_EQUAL(val2, val);
|
||||
|
||||
ok = cmd_parameter_float(8, argv, "p3", &val);
|
||||
CHECK_EQUAL(true, ok);
|
||||
CHECK_EQUAL(val2, val);
|
||||
|
||||
ok = cmd_parameter_float(8, argv, "p4", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
|
||||
ok = cmd_parameter_float(8, argv, "p5", &val);
|
||||
CHECK_EQUAL(false, ok);
|
||||
}
|
||||
TEST(cli, cmd_parameter_last)
|
||||
{
|
||||
char *argv[] = { "cmd", "p1", "p2", "3", "p4", "p5" };
|
||||
CHECK_EQUAL(cmd_parameter_last(6, argv), "p5");
|
||||
}
|
||||
TEST(cli, cmd_has_option)
|
||||
{
|
||||
char *argv[] = { "cmd", "-p", "p2", "3", "p4", "p5" };
|
||||
CHECK_EQUAL(cmd_has_option(6, argv, "-p"), true);
|
||||
}
|
||||
TEST(cli, help)
|
||||
{
|
||||
REQUEST("help");
|
||||
CHECK(strlen(buf) > 20 );
|
||||
}
|
||||
TEST(cli, hello)
|
||||
{
|
||||
REQUEST("echo Hi!");
|
||||
ARRAY_CMP(RESPONSE("Hi! ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo1)
|
||||
{
|
||||
REQUEST(" echo Hi!");
|
||||
ARRAY_CMP(RESPONSE("Hi! ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo2)
|
||||
{
|
||||
REQUEST("echo foo faa");
|
||||
ARRAY_CMP(RESPONSE("foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo3)
|
||||
{
|
||||
REQUEST("echo foo faa");
|
||||
ARRAY_CMP(RESPONSE("foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo4)
|
||||
{
|
||||
REQUEST("echo foo faa");
|
||||
ARRAY_CMP(RESPONSE("foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo5)
|
||||
{
|
||||
REQUEST("echo \"foo faa\"");
|
||||
ARRAY_CMP(RESPONSE("foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo6)
|
||||
{
|
||||
REQUEST("echo \"foo faa");
|
||||
ARRAY_CMP(RESPONSE("\"foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo7)
|
||||
{
|
||||
REQUEST("echo 'foo faa\"");
|
||||
ARRAY_CMP(RESPONSE("'foo faa\" ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo8)
|
||||
{
|
||||
REQUEST("echof\x7f foo faa");
|
||||
ARRAY_CMP(RESPONSE("foo faa ") , buf);
|
||||
}
|
||||
TEST(cli, cmd_echo9)
|
||||
{
|
||||
REQUEST("echo foo faa\x1b[D\x1b[D\x1b[D hello ");
|
||||
ARRAY_CMP(RESPONSE("foo hello faa ") , buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_echo10)
|
||||
{
|
||||
REQUEST("echo foo faa\x1b[D\x1b[C\x1b[C hello "); //echo foo hello faa
|
||||
ARRAY_CMP(RESPONSE("foo faa hello ") , buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_echo11)
|
||||
{
|
||||
REQUEST("echo off\r");
|
||||
INIT_BUF();
|
||||
input("echo test");
|
||||
ARRAY_CMP("" , buf);
|
||||
input("\r");
|
||||
ARRAY_CMP("test \r\n" , buf);
|
||||
INIT_BUF();
|
||||
REQUEST("echo on\r");
|
||||
INIT_BUF();
|
||||
input("e");
|
||||
ARRAY_CMP(CMDLINE("e ") , buf);
|
||||
INIT_BUF();
|
||||
input("c");
|
||||
ARRAY_CMP(CMDLINE("ec ") , buf);
|
||||
INIT_BUF();
|
||||
input("h");
|
||||
ARRAY_CMP(CMDLINE("ech ") , buf);
|
||||
INIT_BUF();
|
||||
input("o");
|
||||
ARRAY_CMP(CMDLINE("echo ") , buf);
|
||||
INIT_BUF();
|
||||
input(" ");
|
||||
ARRAY_CMP(CMDLINE("echo ") , buf);
|
||||
INIT_BUF();
|
||||
input("o");
|
||||
ARRAY_CMP(CMDLINE("echo o ") , buf);
|
||||
INIT_BUF();
|
||||
input("k");
|
||||
ARRAY_CMP(CMDLINE("echo ok ") , buf);
|
||||
CLEAN();
|
||||
}
|
||||
|
||||
TEST(cli, cmd_arrows_up)
|
||||
{
|
||||
REQUEST("echo foo-1");
|
||||
INIT_BUF();
|
||||
input("\x1b[A");
|
||||
ARRAY_CMP(CMDLINE("echo foo-1 ") , buf);
|
||||
INIT_BUF();
|
||||
input("\x1b[A");
|
||||
ARRAY_CMP(CMDLINE("echo foo-1 ") , buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_arrows_up_down)
|
||||
{
|
||||
REQUEST("echo test-1");
|
||||
ARRAY_CMP(RESPONSE("test-1 "), buf);
|
||||
REQUEST("echo test-2");
|
||||
ARRAY_CMP(RESPONSE("test-2 "), buf);
|
||||
REQUEST("echo test-3");
|
||||
ARRAY_CMP(RESPONSE("test-3 "), buf);
|
||||
|
||||
INIT_BUF();
|
||||
UP();
|
||||
ARRAY_CMP(CMDLINE("echo test-3 "), buf);
|
||||
INIT_BUF();
|
||||
UP();
|
||||
ARRAY_CMP(CMDLINE("echo test-2 "), buf);
|
||||
INIT_BUF();
|
||||
UP();
|
||||
ARRAY_CMP(CMDLINE("echo test-1 "), buf);
|
||||
INIT_BUF();
|
||||
UP();
|
||||
ARRAY_CMP(CMDLINE("echo test-1 "), buf);
|
||||
INIT_BUF();
|
||||
DOWN();
|
||||
ARRAY_CMP(CMDLINE("echo test-2 "), buf);
|
||||
INIT_BUF();
|
||||
DOWN();
|
||||
ARRAY_CMP(CMDLINE("echo test-3 "), buf);
|
||||
INIT_BUF();
|
||||
DOWN();
|
||||
ARRAY_CMP(CMDLINE(" "), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_pageup_page_down)
|
||||
{
|
||||
//goto history beginning/end
|
||||
REQUEST("echo test-1");
|
||||
REQUEST("echo test-2");
|
||||
REQUEST("echo test-3");
|
||||
REQUEST("echo test-4");
|
||||
INIT_BUF();
|
||||
PAGE_UP();
|
||||
ARRAY_CMP(CMDLINE("echo test-1 "), buf);
|
||||
INIT_BUF();
|
||||
PAGE_DOWN();
|
||||
ARRAY_CMP(CMDLINE("echo test-4 "), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_text_pageup)
|
||||
{
|
||||
REQUEST("echo test-1");
|
||||
REQUEST("echo test-2");
|
||||
REQUEST("echo test-3");
|
||||
REQUEST("echo test-4");
|
||||
input("hello");
|
||||
INIT_BUF();
|
||||
PAGE_UP(); //goto end of history
|
||||
ARRAY_CMP(CMDLINE("echo test-1 "), buf);
|
||||
INIT_BUF();
|
||||
PAGE_DOWN(); //goto beginning of history - it should be just writted "hello"
|
||||
ARRAY_CMP(CMDLINE("hello "), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_text_pageup_up)
|
||||
{
|
||||
REQUEST("echo test-1");
|
||||
REQUEST("echo test-2");
|
||||
REQUEST("echo test-3");
|
||||
REQUEST("echo test-4");
|
||||
input("hello");
|
||||
INIT_BUF();
|
||||
PAGE_UP(); //goto end of history
|
||||
ARRAY_CMP(CMDLINE("echo test-1 "), buf);
|
||||
INIT_BUF();
|
||||
DOWN();
|
||||
ARRAY_CMP(CMDLINE("echo test-2 "), buf);
|
||||
INIT_BUF();
|
||||
PAGE_DOWN(); //goto beginning of history - it should be just writted "hello"
|
||||
ARRAY_CMP(CMDLINE("hello "), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_text_delete)
|
||||
{
|
||||
input("hello world");
|
||||
LEFT_N(2);
|
||||
DELETE();
|
||||
INIT_BUF();
|
||||
DELETE();
|
||||
ARRAY_CMP(CMDLINE("hello wor "), buf);
|
||||
INIT_BUF();
|
||||
DELETE();
|
||||
ARRAY_CMP(CMDLINE("hello wor "), buf);
|
||||
INIT_BUF();
|
||||
DELETE();
|
||||
ARRAY_CMP(CMDLINE("hello wor "), buf);
|
||||
LEFT_N(2);
|
||||
INIT_BUF();
|
||||
DELETE();
|
||||
ARRAY_CMP(CMDLINE_CUR("hello wr ", "2", BACKWARD), buf);
|
||||
BACKSPACE();
|
||||
BACKSPACE();
|
||||
INIT_BUF();
|
||||
BACKSPACE();
|
||||
ARRAY_CMP(CMDLINE_CUR("hellr ", "2", BACKWARD), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_insert)
|
||||
{
|
||||
CLEAN();
|
||||
input("echo hello word");
|
||||
LEFT();
|
||||
INIT_BUF();
|
||||
input("l");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo hello world ", "2", BACKWARD), buf);
|
||||
LEFT_N(10);
|
||||
INIT_BUF();
|
||||
LEFT();
|
||||
ARRAY_CMP(CMDLINE_CUR("echo hello world ", "13", BACKWARD), buf);
|
||||
INIT_BUF();
|
||||
RIGHT();
|
||||
ARRAY_CMP(CMDLINE_CUR("echo hello world ", "12", BACKWARD), buf);
|
||||
CLEAN();
|
||||
}
|
||||
TEST(cli, cmd_tab_1)
|
||||
{
|
||||
INIT_BUF();
|
||||
input("e");
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo ", "1", BACKWARD) , buf);
|
||||
|
||||
input("\rech");
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo ", "1", BACKWARD) , buf);
|
||||
|
||||
input("\r");
|
||||
}
|
||||
TEST(cli, cmd_tab_2)
|
||||
{
|
||||
INIT_BUF();
|
||||
|
||||
cmd_add("role", cmd_dummy, 0, 0);
|
||||
cmd_add("route", cmd_dummy, 0, 0);
|
||||
cmd_add("rile", cmd_dummy, 0, 0);
|
||||
input("r");
|
||||
INIT_BUF();
|
||||
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("role ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("route ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("rile ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\x1b[Z");
|
||||
ARRAY_CMP(CMDLINE_CUR("route ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\x1b[Z");
|
||||
ARRAY_CMP(CMDLINE_CUR("role ", "1", BACKWARD) , buf);
|
||||
|
||||
input("\r");
|
||||
}
|
||||
TEST(cli, cmd_tab_3)
|
||||
{
|
||||
INIT_BUF();
|
||||
cmd_add("role", cmd_dummy, 0, 0);
|
||||
cmd_alias_add("rose", "role");
|
||||
cmd_alias_add("rope", "rope");
|
||||
|
||||
input("r");
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("role ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("rose ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("rope ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("r ", "1", BACKWARD) , buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("o");
|
||||
ARRAY_CMP(CMDLINE_CUR("ro ", "1", BACKWARD) , buf);
|
||||
|
||||
ESC();
|
||||
INIT_BUF();
|
||||
}
|
||||
TEST(cli, cmd_tab_4)
|
||||
{
|
||||
INIT_BUF();
|
||||
cmd_variable_add("dut1", "hello");
|
||||
|
||||
input("e");
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo ", "1", BACKWARD) , buf);
|
||||
|
||||
input(" $d");
|
||||
INIT_BUF();
|
||||
input("u");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo $du ", "1", BACKWARD), buf);
|
||||
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo $dut1 ", "1", BACKWARD) , buf);
|
||||
|
||||
input("\re");
|
||||
INIT_BUF();
|
||||
input("\t");
|
||||
ARRAY_CMP(CMDLINE_CUR("echo ", "1", BACKWARD) , buf);
|
||||
|
||||
input("\r");
|
||||
INIT_BUF();
|
||||
}
|
||||
// // alias test
|
||||
// extern void replace_alias(const char *str, const char *old_str, const char *new_str);
|
||||
// TEST(cli, cmd_alias_1)
|
||||
// {
|
||||
// char str[] = "hello a men";
|
||||
// replace_alias(str, "a", "b");
|
||||
// ARRAY_CMP("hello a men", str);
|
||||
//
|
||||
// replace_alias(str, "hello", "echo");
|
||||
// ARRAY_CMP("echo a men", str);
|
||||
// INIT_BUF();
|
||||
// }
|
||||
/* @todo this not working yet
|
||||
TEST(cli, cmd_alias_2)
|
||||
{
|
||||
REQUEST("alias foo bar");
|
||||
INIT_BUF();
|
||||
REQUEST("alias");
|
||||
ARRAY_CMP("\r\nalias:\r\nfoo 'bar'\r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
|
||||
REQUEST("alias foo");
|
||||
INIT_BUF();
|
||||
REQUEST("alias");
|
||||
ARRAY_CMP("\r\nalias:\r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
}
|
||||
*/
|
||||
TEST(cli, cmd_alias_3)
|
||||
{
|
||||
cmd_alias_add("p", "echo");
|
||||
REQUEST("p toimii");
|
||||
ARRAY_CMP("\r\ntoimii \r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
|
||||
cmd_alias_add("printtti", "echo");
|
||||
REQUEST("printtti toimii");
|
||||
ARRAY_CMP("\r\ntoimii \r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
}
|
||||
TEST(cli, cmd_alias_4)
|
||||
{
|
||||
REQUEST("alias dut1 \"echo dut1\"");
|
||||
REQUEST("alias dut2 \"echo dut2\"");
|
||||
REQUEST("alias dut3 \"echo dut3\"");
|
||||
REQUEST("dut1");
|
||||
ARRAY_CMP(RESPONSE("dut1 "), buf);
|
||||
}
|
||||
TEST(cli, cmd_series)
|
||||
{
|
||||
REQUEST("alias dut1 \"echo dut1\"");
|
||||
REQUEST("alias dut2 \"echo dut2\"");
|
||||
REQUEST("alias dut3 \"echo dut3\"");
|
||||
REQUEST("dut1;dut2;dut3");
|
||||
ARRAY_CMP(RESPONSE("dut1 \r\ndut2 \r\ndut3 "), buf);
|
||||
}
|
||||
|
||||
TEST(cli, cmd_var_1)
|
||||
{
|
||||
REQUEST("set foo \"bar test\"");
|
||||
INIT_BUF();
|
||||
REQUEST("set");
|
||||
ARRAY_CMP("\r\nvariables:\r\nfoo 'bar test'\r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
|
||||
REQUEST("set foo");
|
||||
INIT_BUF();
|
||||
REQUEST("set");
|
||||
ARRAY_CMP("\r\nvariables:\r\n\r\x1b[2K/> \x1b[1D", buf);
|
||||
}
|
||||
TEST(cli, cmd_var_2)
|
||||
{
|
||||
REQUEST("set foo \"hello world\"");
|
||||
REQUEST("echo foo");
|
||||
ARRAY_CMP(RESPONSE("foo ") , buf);
|
||||
|
||||
REQUEST("echo $foo");
|
||||
ARRAY_CMP(RESPONSE("hello world ") , buf);
|
||||
|
||||
REQUEST("set faa !");
|
||||
REQUEST("echo $foo$faa");
|
||||
ARRAY_CMP(RESPONSE("hello world! ") , buf);
|
||||
}
|
||||
TEST(cli, multiple_cmd)
|
||||
{
|
||||
REQUEST("set foo \"hello world\";echo $foo");
|
||||
ARRAY_CMP(RESPONSE("hello world ") , buf);
|
||||
|
||||
REQUEST("setd faa \"hello world\";echo $faa");
|
||||
ARRAY_CMP("\r\nCommand 'setd' not found.\r\n$faa \r\n\r\x1B[2K/> \x1B[1D" , buf);
|
||||
|
||||
REQUEST("setd foo \"hello guy\"&&echo $foo");
|
||||
ARRAY_CMP(RESPONSE("Command 'setd' not found.") , buf);
|
||||
}
|
||||
TEST(cli, maxlength)
|
||||
{
|
||||
int i;
|
||||
char test_data[600];
|
||||
char *ptr = test_data;
|
||||
strcpy(test_data, "echo ");
|
||||
for (i = 5; i < 600; i++) {
|
||||
test_data[i] = 'A' + i % 26;
|
||||
}
|
||||
test_data[599] = 0;
|
||||
REQUEST(ptr);
|
||||
//ARRAY_CMP( RESPONSE((test_data+5)), buf);
|
||||
}
|
||||
TEST(cli, ampersand)
|
||||
{
|
||||
REQUEST("echo hello world&");
|
||||
ARRAY_CMP(RESPONSE("hello world ") , buf);
|
||||
}
|
||||
|
||||
#define REDIR_DATA "echo Hi!"
|
||||
#define PASSTHROUGH_BUF_LENGTH 10
|
||||
char passthrough_buffer[PASSTHROUGH_BUF_LENGTH];
|
||||
char* passthrough_ptr = NULL;
|
||||
void passthrough_cb(uint8_t c)
|
||||
{
|
||||
if (passthrough_ptr != NULL) {
|
||||
*passthrough_ptr++ = c;
|
||||
}
|
||||
}
|
||||
TEST(cli, passthrough_set)
|
||||
{
|
||||
passthrough_ptr = passthrough_buffer;
|
||||
memset(&passthrough_buffer, 0, PASSTHROUGH_BUF_LENGTH);
|
||||
INIT_BUF();
|
||||
|
||||
cmd_input_passthrough_func(passthrough_cb);
|
||||
input(REDIR_DATA);
|
||||
|
||||
CHECK(strlen(buf) == 0);
|
||||
ARRAY_CMP(REDIR_DATA, passthrough_buffer);
|
||||
|
||||
cmd_input_passthrough_func(NULL);
|
||||
|
||||
REQUEST(REDIR_DATA);
|
||||
ARRAY_CMP(RESPONSE("Hi! ") , buf);
|
||||
}
|
||||
|
||||
|
||||
TEST(cli, cmd_out_func_set_null)
|
||||
{
|
||||
cmd_out_func(NULL);
|
||||
}
|
||||
|
||||
static int outf_called = 0;
|
||||
void outf(const char *fmt, va_list ap) {
|
||||
outf_called++;
|
||||
}
|
||||
TEST(cli, cmd_out_func_set)
|
||||
{
|
||||
outf_called = 0;
|
||||
cmd_out_func(&outf);
|
||||
cmd_vprintf(NULL, NULL);
|
||||
CHECK_EQUAL(outf_called, 1);
|
||||
}
|
||||
|
||||
TEST(cli, cmd_ctrl_func_set_null)
|
||||
{
|
||||
cmd_ctrl_func(NULL);
|
||||
}
|
||||
|
||||
TEST(cli, cmd_delete_null)
|
||||
{
|
||||
cmd_delete(NULL);
|
||||
}
|
||||
|
||||
TEST(cli, cmd_history_size_set)
|
||||
{
|
||||
cmd_history_size(0);
|
||||
CHECK_EQUAL(cmd_history_size(1), 1);
|
||||
}
|
||||
|
||||
TEST(cli, cmd_add_invalid_params)
|
||||
{
|
||||
cmd_add(NULL, cmd_dummy, NULL, NULL);
|
||||
cmd_add("", cmd_dummy, NULL, NULL);
|
||||
cmd_add("abc", NULL, NULL, NULL);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"testcases": [
|
||||
{
|
||||
"config": {
|
||||
"requirements": {
|
||||
"duts": {
|
||||
"*": {
|
||||
"count": 1,
|
||||
"application": {
|
||||
"bin": "/Users/ollpuo01/git/mbed-os-icetea-integration/BUILD/tests/K64F/GCC_ARM/./TEST_APPS/device/exampleapp/exampleapp.bin",
|
||||
"name": "TEST_APPS-device-exampleapp"
|
||||
},
|
||||
"type": "hardware",
|
||||
"allowed_platforms": [
|
||||
"K64F"
|
||||
]
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"apps": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "test_cmdline"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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=(',', ': '))
|
||||
|
|
|
@ -69,6 +69,7 @@ LEGACY_IGNORE_DIRS = set([
|
|||
|
||||
# Tests, here for simplicity
|
||||
'TESTS',
|
||||
'TEST_APPS',
|
||||
])
|
||||
LEGACY_TOOLCHAIN_NAMES = {
|
||||
'ARM_STD':'ARM',
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
#! /usr/bin/env python2
|
||||
"""
|
||||
mbed SDK
|
||||
Copyright (c) 2011-2013 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 application 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)
|
|
@ -16,27 +16,29 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
|
||||
|
||||
TEST BUILD & RUN
|
||||
TEST BUILD
|
||||
"""
|
||||
from __future__ import print_function, division, absolute_import
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
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 +113,19 @@ if __name__ == '__main__':
|
|||
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 +138,13 @@ if __name__ == '__main__':
|
|||
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 +176,13 @@ if __name__ == '__main__':
|
|||
|
||||
# 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 +273,7 @@ if __name__ == '__main__':
|
|||
|
||||
# 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 +305,4 @@ if __name__ == '__main__':
|
|||
traceback.print_exc(file=sys.stdout)
|
||||
print("[ERROR] %s" % str(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
test_suite.json
|
|
@ -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.
|
||||
"""
|
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
|
@ -0,0 +1 @@
|
|||
This folder contains hardware test data for icetea integration
|
|
@ -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.
|
||||
"""
|
|
@ -0,0 +1,49 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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 onely 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
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builds": []
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
mbed SDK
|
||||
Copyright (c) 2016 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
|
|
@ -0,0 +1,146 @@
|
|||
"""
|
||||
mbed SDK
|
||||
Copyright (c) 2016 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)
|
|
@ -28,14 +28,17 @@ import pprint
|
|||
import random
|
||||
import argparse
|
||||
import datetime
|
||||
import errno
|
||||
import threading
|
||||
import ctypes
|
||||
import functools
|
||||
import subprocess
|
||||
from colorama import Fore, Back, Style
|
||||
from prettytable import PrettyTable
|
||||
from copy import copy, deepcopy
|
||||
|
||||
from time import sleep, time
|
||||
|
||||
try:
|
||||
from Queue import Queue, Empty
|
||||
except ImportError:
|
||||
|
@ -114,6 +117,7 @@ class ProcessObserver(Thread):
|
|||
class SingleTestExecutor(threading.Thread):
|
||||
""" Example: Single test class in separate thread usage
|
||||
"""
|
||||
|
||||
def __init__(self, single_test):
|
||||
self.single_test = single_test
|
||||
threading.Thread.__init__(self)
|
||||
|
@ -134,7 +138,7 @@ class SingleTestExecutor(threading.Thread):
|
|||
# table shows text x toolchain test result matrix
|
||||
print(self.single_test.generate_test_summary_by_target(
|
||||
test_summary, shuffle_seed))
|
||||
print("Completed in %.2f sec"% (elapsed_time))
|
||||
print("Completed in %.2f sec" % (elapsed_time))
|
||||
|
||||
|
||||
class SingleTestRunner(object):
|
||||
|
@ -157,26 +161,26 @@ class SingleTestRunner(object):
|
|||
TEST_RESULT_NOT_SUPPORTED = "NOT_SUPPORTED"
|
||||
|
||||
GLOBAL_LOOPS_COUNT = 1 # How many times each test should be repeated
|
||||
TEST_LOOPS_LIST = [] # We redefine no.of loops per test_id
|
||||
TEST_LOOPS_DICT = {} # TEST_LOOPS_LIST in dict format: { test_id : test_loop_count}
|
||||
TEST_LOOPS_LIST = [] # We redefine no.of loops per test_id
|
||||
TEST_LOOPS_DICT = {} # TEST_LOOPS_LIST in dict format: { test_id : test_loop_count}
|
||||
|
||||
muts = {} # MUTs descriptor (from external file)
|
||||
test_spec = {} # Test specification (from external file)
|
||||
muts = {} # MUTs descriptor (from external file)
|
||||
test_spec = {} # Test specification (from external file)
|
||||
|
||||
# mbed test suite -> SingleTestRunner
|
||||
TEST_RESULT_MAPPING = {"success" : TEST_RESULT_OK,
|
||||
"failure" : TEST_RESULT_FAIL,
|
||||
"error" : TEST_RESULT_ERROR,
|
||||
"ioerr_copy" : TEST_RESULT_IOERR_COPY,
|
||||
"ioerr_disk" : TEST_RESULT_IOERR_DISK,
|
||||
"ioerr_serial" : TEST_RESULT_IOERR_SERIAL,
|
||||
"timeout" : TEST_RESULT_TIMEOUT,
|
||||
"no_image" : TEST_RESULT_NO_IMAGE,
|
||||
"end" : TEST_RESULT_UNDEF,
|
||||
"mbed_assert" : TEST_RESULT_MBED_ASSERT,
|
||||
"build_failed" : TEST_RESULT_BUILD_FAILED,
|
||||
"not_supproted" : TEST_RESULT_NOT_SUPPORTED
|
||||
}
|
||||
TEST_RESULT_MAPPING = {"success": TEST_RESULT_OK,
|
||||
"failure": TEST_RESULT_FAIL,
|
||||
"error": TEST_RESULT_ERROR,
|
||||
"ioerr_copy": TEST_RESULT_IOERR_COPY,
|
||||
"ioerr_disk": TEST_RESULT_IOERR_DISK,
|
||||
"ioerr_serial": TEST_RESULT_IOERR_SERIAL,
|
||||
"timeout": TEST_RESULT_TIMEOUT,
|
||||
"no_image": TEST_RESULT_NO_IMAGE,
|
||||
"end": TEST_RESULT_UNDEF,
|
||||
"mbed_assert": TEST_RESULT_MBED_ASSERT,
|
||||
"build_failed": TEST_RESULT_BUILD_FAILED,
|
||||
"not_supproted": TEST_RESULT_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
_global_loops_count=1,
|
||||
|
@ -286,17 +290,18 @@ class SingleTestRunner(object):
|
|||
|
||||
# Database related initializations
|
||||
self.db_logger = factory_db_logger(self.opts_db_url)
|
||||
self.db_logger_build_id = None # Build ID (database index of build_id table)
|
||||
self.db_logger_build_id = None # Build ID (database index of build_id table)
|
||||
# Let's connect to database to set up credentials and confirm database is ready
|
||||
if self.db_logger:
|
||||
self.db_logger.connect_url(self.opts_db_url) # Save db access info inside db_logger object
|
||||
self.db_logger.connect_url(self.opts_db_url) # Save db access info inside db_logger object
|
||||
if self.db_logger.is_connected():
|
||||
# Get hostname and uname so we can use it as build description
|
||||
# when creating new build_id in external database
|
||||
(_hostname, _uname) = self.db_logger.get_hostname()
|
||||
_host_location = os.path.dirname(os.path.abspath(__file__))
|
||||
build_id_type = None if self.opts_only_build_tests is None else self.db_logger.BUILD_ID_TYPE_BUILD_ONLY
|
||||
self.db_logger_build_id = self.db_logger.get_next_build_id(_hostname, desc=_uname, location=_host_location, type=build_id_type)
|
||||
self.db_logger_build_id = self.db_logger.get_next_build_id(_hostname, desc=_uname,
|
||||
location=_host_location, type=build_id_type)
|
||||
self.db_logger.disconnect()
|
||||
|
||||
def dump_options(self):
|
||||
|
@ -307,23 +312,23 @@ class SingleTestRunner(object):
|
|||
or
|
||||
data_str = json.dumps(self.dump_options())
|
||||
"""
|
||||
result = {"db_url" : str(self.opts_db_url),
|
||||
"log_file_name" : str(self.opts_log_file_name),
|
||||
"shuffle_test_order" : str(self.opts_shuffle_test_order),
|
||||
"shuffle_test_seed" : str(self.opts_shuffle_test_seed),
|
||||
"test_by_names" : str(self.opts_test_by_names),
|
||||
"peripheral_by_names" : str(self.opts_peripheral_by_names),
|
||||
"test_only_peripheral" : str(self.opts_test_only_peripheral),
|
||||
"test_only_common" : str(self.opts_test_only_common),
|
||||
"verbose" : str(self.opts_verbose),
|
||||
"firmware_global_name" : str(self.opts_firmware_global_name),
|
||||
"only_build_tests" : str(self.opts_only_build_tests),
|
||||
"copy_method" : str(self.opts_copy_method),
|
||||
"mut_reset_type" : str(self.opts_mut_reset_type),
|
||||
"jobs" : str(self.opts_jobs),
|
||||
"extend_test_timeout" : str(self.opts_extend_test_timeout),
|
||||
"_dummy" : ''
|
||||
}
|
||||
result = {"db_url": str(self.opts_db_url),
|
||||
"log_file_name": str(self.opts_log_file_name),
|
||||
"shuffle_test_order": str(self.opts_shuffle_test_order),
|
||||
"shuffle_test_seed": str(self.opts_shuffle_test_seed),
|
||||
"test_by_names": str(self.opts_test_by_names),
|
||||
"peripheral_by_names": str(self.opts_peripheral_by_names),
|
||||
"test_only_peripheral": str(self.opts_test_only_peripheral),
|
||||
"test_only_common": str(self.opts_test_only_common),
|
||||
"verbose": str(self.opts_verbose),
|
||||
"firmware_global_name": str(self.opts_firmware_global_name),
|
||||
"only_build_tests": str(self.opts_only_build_tests),
|
||||
"copy_method": str(self.opts_copy_method),
|
||||
"mut_reset_type": str(self.opts_mut_reset_type),
|
||||
"jobs": str(self.opts_jobs),
|
||||
"extend_test_timeout": str(self.opts_extend_test_timeout),
|
||||
"_dummy": ''
|
||||
}
|
||||
return result
|
||||
|
||||
def shuffle_random_func(self):
|
||||
|
@ -365,7 +370,6 @@ class SingleTestRunner(object):
|
|||
'shuffle_random_seed': self.shuffle_random_seed
|
||||
}
|
||||
|
||||
|
||||
# print '=== %s::%s ===' % (target, toolchain)
|
||||
# Let's build our test
|
||||
if target not in TARGET_MAP:
|
||||
|
@ -395,7 +399,7 @@ class SingleTestRunner(object):
|
|||
print(self.logger.log_line(
|
||||
self.logger.LogType.NOTIF,
|
||||
'Skipped tests for %s target. Toolchain %s is not '
|
||||
'supported for this target'% (T.name, toolchain)))
|
||||
'supported for this target' % (T.name, toolchain)))
|
||||
continue
|
||||
|
||||
except ToolException:
|
||||
|
@ -438,13 +442,13 @@ class SingleTestRunner(object):
|
|||
_extra=json.dumps(self.dump_options()))
|
||||
self.db_logger.disconnect();
|
||||
|
||||
valid_test_map_keys = self.get_valid_tests(test_map_keys, target, toolchain, test_ids, self.opts_include_non_automated)
|
||||
valid_test_map_keys = self.get_valid_tests(test_map_keys, target, toolchain, test_ids,
|
||||
self.opts_include_non_automated)
|
||||
skipped_test_map_keys = self.get_skipped_tests(test_map_keys, valid_test_map_keys)
|
||||
|
||||
for skipped_test_id in skipped_test_map_keys:
|
||||
test_suite_properties['skipped'].append(skipped_test_id)
|
||||
|
||||
|
||||
# First pass through all tests and determine which libraries need to be built
|
||||
libraries = []
|
||||
for test_id in valid_test_map_keys:
|
||||
|
@ -456,7 +460,6 @@ class SingleTestRunner(object):
|
|||
if lib['build_dir'] in test.dependencies and lib['id'] not in libraries:
|
||||
libraries.append(lib['id'])
|
||||
|
||||
|
||||
clean_project_options = True if self.opts_goanna_for_tests or clean or self.opts_clean else None
|
||||
|
||||
# Build all required libraries
|
||||
|
@ -478,11 +481,10 @@ class SingleTestRunner(object):
|
|||
'There were errors while building library %s' % lib_id))
|
||||
continue
|
||||
|
||||
|
||||
for test_id in valid_test_map_keys:
|
||||
test = TEST_MAP[test_id]
|
||||
|
||||
test_suite_properties['test.libs.%s.%s.%s'% (target, toolchain, test_id)] = ', '.join(libraries)
|
||||
test_suite_properties['test.libs.%s.%s.%s' % (target, toolchain, test_id)] = ', '.join(libraries)
|
||||
|
||||
# TODO: move this 2 below loops to separate function
|
||||
INC_DIRS = []
|
||||
|
@ -494,18 +496,18 @@ class SingleTestRunner(object):
|
|||
for lib_id in libraries:
|
||||
if 'macros' in LIBRARY_MAP[lib_id] and LIBRARY_MAP[lib_id]['macros']:
|
||||
MACROS.extend(LIBRARY_MAP[lib_id]['macros'])
|
||||
MACROS.append('TEST_SUITE_TARGET_NAME="%s"'% target)
|
||||
MACROS.append('TEST_SUITE_TEST_ID="%s"'% test_id)
|
||||
MACROS.append('TEST_SUITE_TARGET_NAME="%s"' % target)
|
||||
MACROS.append('TEST_SUITE_TEST_ID="%s"' % test_id)
|
||||
test_uuid = uuid.uuid4()
|
||||
MACROS.append('TEST_SUITE_UUID="%s"'% str(test_uuid))
|
||||
MACROS.append('TEST_SUITE_UUID="%s"' % str(test_uuid))
|
||||
|
||||
# Prepare extended test results data structure (it can be used to generate detailed test report)
|
||||
if target not in self.test_summary_ext:
|
||||
self.test_summary_ext[target] = {} # test_summary_ext : toolchain
|
||||
if toolchain not in self.test_summary_ext[target]:
|
||||
self.test_summary_ext[target][toolchain] = {} # test_summary_ext : toolchain : target
|
||||
self.test_summary_ext[target][toolchain] = {} # test_summary_ext : toolchain : target
|
||||
|
||||
tt_test_id = "%s::%s::%s" % (toolchain, target, test_id) # For logging only
|
||||
tt_test_id = "%s::%s::%s" % (toolchain, target, test_id) # For logging only
|
||||
|
||||
project_name = self.opts_firmware_global_name if self.opts_firmware_global_name else None
|
||||
try:
|
||||
|
@ -523,7 +525,6 @@ class SingleTestRunner(object):
|
|||
except Exception as e:
|
||||
project_name_str = project_name if project_name is not None else test_id
|
||||
|
||||
|
||||
test_result = self.TEST_RESULT_FAIL
|
||||
|
||||
if isinstance(e, ToolException):
|
||||
|
@ -538,7 +539,6 @@ class SingleTestRunner(object):
|
|||
'Project %s is not supported' % project_name_str))
|
||||
test_result = self.TEST_RESULT_NOT_SUPPORTED
|
||||
|
||||
|
||||
# Append test results to global test summary
|
||||
self.test_summary.append(
|
||||
(test_result, target, toolchain, test_id,
|
||||
|
@ -549,17 +549,17 @@ class SingleTestRunner(object):
|
|||
if test_id not in self.test_summary_ext[target][toolchain]:
|
||||
self.test_summary_ext[target][toolchain][test_id] = []
|
||||
|
||||
self.test_summary_ext[target][toolchain][test_id].append({ 0: {
|
||||
'result' : test_result,
|
||||
'output' : '',
|
||||
'target_name' : target,
|
||||
self.test_summary_ext[target][toolchain][test_id].append({0: {
|
||||
'result': test_result,
|
||||
'output': '',
|
||||
'target_name': target,
|
||||
'target_name_unique': target,
|
||||
'toolchain_name' : toolchain,
|
||||
'id' : test_id,
|
||||
'description' : test.get_description(),
|
||||
'elapsed_time' : 0,
|
||||
'duration' : 0,
|
||||
'copy_method' : None
|
||||
'toolchain_name': toolchain,
|
||||
'id': test_id,
|
||||
'description': test.get_description(),
|
||||
'elapsed_time': 0,
|
||||
'duration': 0,
|
||||
'copy_method': None
|
||||
}})
|
||||
continue
|
||||
|
||||
|
@ -577,9 +577,9 @@ class SingleTestRunner(object):
|
|||
test_spec = self.shape_test_request(target, path, test_id, test_duration)
|
||||
test_loops = self.get_test_loop_count(test_id)
|
||||
|
||||
test_suite_properties['test.duration.%s.%s.%s'% (target, toolchain, test_id)] = test_duration
|
||||
test_suite_properties['test.loops.%s.%s.%s'% (target, toolchain, test_id)] = test_loops
|
||||
test_suite_properties['test.path.%s.%s.%s'% (target, toolchain, test_id)] = path
|
||||
test_suite_properties['test.duration.%s.%s.%s' % (target, toolchain, test_id)] = test_duration
|
||||
test_suite_properties['test.loops.%s.%s.%s' % (target, toolchain, test_id)] = test_loops
|
||||
test_suite_properties['test.path.%s.%s.%s' % (target, toolchain, test_id)] = path
|
||||
|
||||
# read MUTs, test specification and perform tests
|
||||
handle_results = self.handle(test_spec, target, toolchain, test_loops=test_loops)
|
||||
|
@ -627,12 +627,11 @@ class SingleTestRunner(object):
|
|||
if self.opts_shuffle_test_seed is not None and self.is_shuffle_seed_float():
|
||||
self.shuffle_random_seed = round(float(self.opts_shuffle_test_seed), self.SHUFFLE_SEED_ROUND)
|
||||
|
||||
|
||||
if self.opts_parallel_test_exec:
|
||||
###################################################################
|
||||
# Experimental, parallel test execution per singletest instance.
|
||||
###################################################################
|
||||
execute_threads = [] # Threads used to build mbed SDL, libs, test cases and execute tests
|
||||
execute_threads = [] # Threads used to build mbed SDL, libs, test cases and execute tests
|
||||
# Note: We are building here in parallel for each target separately!
|
||||
# So we are not building the same thing multiple times and compilers
|
||||
# in separate threads do not collide.
|
||||
|
@ -640,26 +639,29 @@ class SingleTestRunner(object):
|
|||
# get information about available MUTs (per target).
|
||||
for target, toolchains in self.test_spec['targets'].items():
|
||||
self.test_suite_properties_ext[target] = {}
|
||||
t = threading.Thread(target=self.execute_thread_slice, args = (q, target, toolchains, clean, test_ids, self.build_report, self.build_properties))
|
||||
t = threading.Thread(target=self.execute_thread_slice, args=(
|
||||
q, target, toolchains, clean, test_ids, self.build_report, self.build_properties))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
execute_threads.append(t)
|
||||
|
||||
for t in execute_threads:
|
||||
q.get() # t.join() would block some threads because we should not wait in any order for thread end
|
||||
q.get() # t.join() would block some threads because we should not wait in any order for thread end
|
||||
else:
|
||||
# Serialized (not parallel) test execution
|
||||
for target, toolchains in self.test_spec['targets'].items():
|
||||
if target not in self.test_suite_properties_ext:
|
||||
self.test_suite_properties_ext[target] = {}
|
||||
|
||||
self.execute_thread_slice(q, target, toolchains, clean, test_ids, self.build_report, self.build_properties)
|
||||
self.execute_thread_slice(q, target, toolchains, clean, test_ids, self.build_report,
|
||||
self.build_properties)
|
||||
q.get()
|
||||
|
||||
if self.db_logger:
|
||||
self.db_logger.reconnect();
|
||||
if self.db_logger.is_connected():
|
||||
self.db_logger.update_build_id_info(self.db_logger_build_id, _status_fk=self.db_logger.BUILD_ID_STATUS_COMPLETED)
|
||||
self.db_logger.update_build_id_info(self.db_logger_build_id,
|
||||
_status_fk=self.db_logger.BUILD_ID_STATUS_COMPLETED)
|
||||
self.db_logger.disconnect();
|
||||
|
||||
return self.test_summary, self.shuffle_random_seed, self.test_summary_ext, self.test_suite_properties_ext, self.build_report, self.build_properties
|
||||
|
@ -683,8 +685,8 @@ class SingleTestRunner(object):
|
|||
continue
|
||||
|
||||
if (self.opts_peripheral_by_names and test.peripherals and
|
||||
not any((i in self.opts_peripheral_by_names)
|
||||
for i in test.peripherals)):
|
||||
not any((i in self.opts_peripheral_by_names)
|
||||
for i in test.peripherals)):
|
||||
# We will skip tests not forced with -p option
|
||||
if self.opts_verbose_skipped_tests:
|
||||
print(self.logger.log_line(
|
||||
|
@ -755,7 +757,7 @@ class SingleTestRunner(object):
|
|||
|
||||
result = "Test summary:\n"
|
||||
for target in unique_targets:
|
||||
result_dict = {} # test : { toolchain : result }
|
||||
result_dict = {} # test : { toolchain : result }
|
||||
unique_target_toolchains = []
|
||||
for test in test_summary:
|
||||
if test[TARGET_INDEX] == target:
|
||||
|
@ -769,7 +771,7 @@ class SingleTestRunner(object):
|
|||
pt = PrettyTable(pt_cols)
|
||||
for col in pt_cols:
|
||||
pt.align[col] = "l"
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
|
||||
for test in unique_tests:
|
||||
if test in result_dict:
|
||||
|
@ -781,40 +783,40 @@ class SingleTestRunner(object):
|
|||
row.append(test_results[toolchain])
|
||||
pt.add_row(row)
|
||||
result += pt.get_string()
|
||||
shuffle_seed_text = "Shuffle Seed: %.*f"% (self.SHUFFLE_SEED_ROUND,
|
||||
shuffle_seed if shuffle_seed else self.shuffle_random_seed)
|
||||
result += "\n%s"% (shuffle_seed_text if self.opts_shuffle_test_order else '')
|
||||
shuffle_seed_text = "Shuffle Seed: %.*f" % (self.SHUFFLE_SEED_ROUND,
|
||||
shuffle_seed if shuffle_seed else self.shuffle_random_seed)
|
||||
result += "\n%s" % (shuffle_seed_text if self.opts_shuffle_test_order else '')
|
||||
return result
|
||||
|
||||
def generate_test_summary(self, test_summary, shuffle_seed=None):
|
||||
""" Prints well-formed summary with results (SQL table like)
|
||||
table shows target x test results matrix across
|
||||
"""
|
||||
success_code = 0 # Success code that can be leter returned to
|
||||
success_code = 0 # Success code that can be leter returned to
|
||||
result = "Test summary:\n"
|
||||
# Pretty table package is used to print results
|
||||
pt = PrettyTable(["Result", "Target", "Toolchain", "Test ID", "Test Description",
|
||||
"Elapsed Time (sec)", "Timeout (sec)", "Loops"])
|
||||
pt.align["Result"] = "l" # Left align
|
||||
pt.align["Target"] = "l" # Left align
|
||||
pt.align["Toolchain"] = "l" # Left align
|
||||
pt.align["Test ID"] = "l" # Left align
|
||||
pt.align["Test Description"] = "l" # Left align
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
pt.align["Result"] = "l" # Left align
|
||||
pt.align["Target"] = "l" # Left align
|
||||
pt.align["Toolchain"] = "l" # Left align
|
||||
pt.align["Test ID"] = "l" # Left align
|
||||
pt.align["Test Description"] = "l" # Left align
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
|
||||
result_dict = {self.TEST_RESULT_OK : 0,
|
||||
self.TEST_RESULT_FAIL : 0,
|
||||
self.TEST_RESULT_ERROR : 0,
|
||||
self.TEST_RESULT_UNDEF : 0,
|
||||
self.TEST_RESULT_IOERR_COPY : 0,
|
||||
self.TEST_RESULT_IOERR_DISK : 0,
|
||||
self.TEST_RESULT_IOERR_SERIAL : 0,
|
||||
self.TEST_RESULT_NO_IMAGE : 0,
|
||||
self.TEST_RESULT_TIMEOUT : 0,
|
||||
self.TEST_RESULT_MBED_ASSERT : 0,
|
||||
self.TEST_RESULT_BUILD_FAILED : 0,
|
||||
self.TEST_RESULT_NOT_SUPPORTED : 0
|
||||
}
|
||||
result_dict = {self.TEST_RESULT_OK: 0,
|
||||
self.TEST_RESULT_FAIL: 0,
|
||||
self.TEST_RESULT_ERROR: 0,
|
||||
self.TEST_RESULT_UNDEF: 0,
|
||||
self.TEST_RESULT_IOERR_COPY: 0,
|
||||
self.TEST_RESULT_IOERR_DISK: 0,
|
||||
self.TEST_RESULT_IOERR_SERIAL: 0,
|
||||
self.TEST_RESULT_NO_IMAGE: 0,
|
||||
self.TEST_RESULT_TIMEOUT: 0,
|
||||
self.TEST_RESULT_MBED_ASSERT: 0,
|
||||
self.TEST_RESULT_BUILD_FAILED: 0,
|
||||
self.TEST_RESULT_NOT_SUPPORTED: 0
|
||||
}
|
||||
|
||||
for test in test_summary:
|
||||
if test[0] in result_dict:
|
||||
|
@ -824,10 +826,11 @@ class SingleTestRunner(object):
|
|||
result += "\n"
|
||||
|
||||
# Print result count
|
||||
result += "Result: " + ' / '.join(['%s %s' % (value, key) for (key, value) in {k: v for k, v in result_dict.items() if v != 0}.items()])
|
||||
shuffle_seed_text = "Shuffle Seed: %.*f\n"% (self.SHUFFLE_SEED_ROUND,
|
||||
shuffle_seed if shuffle_seed else self.shuffle_random_seed)
|
||||
result += "\n%s"% (shuffle_seed_text if self.opts_shuffle_test_order else '')
|
||||
result += "Result: " + ' / '.join(
|
||||
['%s %s' % (value, key) for (key, value) in {k: v for k, v in result_dict.items() if v != 0}.items()])
|
||||
shuffle_seed_text = "Shuffle Seed: %.*f\n" % (self.SHUFFLE_SEED_ROUND,
|
||||
shuffle_seed if shuffle_seed else self.shuffle_random_seed)
|
||||
result += "\n%s" % (shuffle_seed_text if self.opts_shuffle_test_order else '')
|
||||
return result
|
||||
|
||||
def test_loop_list_to_dict(self, test_loops_str):
|
||||
|
@ -883,7 +886,7 @@ class SingleTestRunner(object):
|
|||
return None
|
||||
|
||||
mcu = mut['mcu']
|
||||
copy_method = mut.get('copy_method') # Available board configuration selection e.g. core selection etc.
|
||||
copy_method = mut.get('copy_method') # Available board configuration selection e.g. core selection etc.
|
||||
|
||||
if self.db_logger:
|
||||
self.db_logger.reconnect()
|
||||
|
@ -939,13 +942,14 @@ class SingleTestRunner(object):
|
|||
# Host test execution
|
||||
start_host_exec_time = time()
|
||||
|
||||
single_test_result = self.TEST_RESULT_UNDEF # single test run result
|
||||
single_test_result = self.TEST_RESULT_UNDEF # single test run result
|
||||
_copy_method = selected_copy_method
|
||||
|
||||
if not exists(image_path):
|
||||
single_test_result = self.TEST_RESULT_NO_IMAGE
|
||||
elapsed_time = 0
|
||||
single_test_output = self.logger.log_line(self.logger.LogType.ERROR, 'Image file does not exist: %s'% image_path)
|
||||
single_test_output = self.logger.log_line(self.logger.LogType.ERROR,
|
||||
'Image file does not exist: %s' % image_path)
|
||||
print(single_test_output)
|
||||
else:
|
||||
# Host test execution
|
||||
|
@ -965,20 +969,20 @@ class SingleTestRunner(object):
|
|||
|
||||
# Store test result
|
||||
test_all_result.append(single_test_result)
|
||||
total_elapsed_time = time() - start_host_exec_time # Test time with copy (flashing) / reset
|
||||
total_elapsed_time = time() - start_host_exec_time # Test time with copy (flashing) / reset
|
||||
elapsed_time = single_testduration # TIme of single test case execution after reset
|
||||
|
||||
detailed_test_results[test_index] = {
|
||||
'result' : single_test_result,
|
||||
'output' : single_test_output,
|
||||
'target_name' : target_name,
|
||||
'target_name_unique' : target_name_unique,
|
||||
'toolchain_name' : toolchain_name,
|
||||
'id' : test_id,
|
||||
'description' : test_description,
|
||||
'elapsed_time' : round(elapsed_time, 2),
|
||||
'duration' : single_timeout,
|
||||
'copy_method' : _copy_method,
|
||||
'result': single_test_result,
|
||||
'output': single_test_output,
|
||||
'target_name': target_name,
|
||||
'target_name_unique': target_name_unique,
|
||||
'toolchain_name': toolchain_name,
|
||||
'id': test_id,
|
||||
'description': test_description,
|
||||
'elapsed_time': round(elapsed_time, 2),
|
||||
'duration': single_timeout,
|
||||
'copy_method': _copy_method,
|
||||
}
|
||||
|
||||
print(self.print_test_result(
|
||||
|
@ -1006,7 +1010,8 @@ class SingleTestRunner(object):
|
|||
if self.db_logger:
|
||||
self.db_logger.disconnect()
|
||||
|
||||
return (self.shape_global_test_loop_result(test_all_result, self.opts_waterfall_test and self.opts_consolidate_waterfall_test),
|
||||
return (self.shape_global_test_loop_result(test_all_result,
|
||||
self.opts_waterfall_test and self.opts_consolidate_waterfall_test),
|
||||
target_name_unique,
|
||||
toolchain_name,
|
||||
test_id,
|
||||
|
@ -1044,7 +1049,7 @@ class SingleTestRunner(object):
|
|||
tokens.append(test_description)
|
||||
separator = "::"
|
||||
time_info = " in %.2f of %d sec" % (round(elapsed_time, 2), duration)
|
||||
result = separator.join(tokens) + " [" + test_result +"]" + time_info
|
||||
result = separator.join(tokens) + " [" + test_result + "]" + time_info
|
||||
return Fore.MAGENTA + result + Fore.RESET
|
||||
|
||||
def shape_test_loop_ok_result_count(self, test_all_result):
|
||||
|
@ -1052,7 +1057,7 @@ class SingleTestRunner(object):
|
|||
"""
|
||||
test_loop_count = len(test_all_result)
|
||||
test_loop_ok_result = test_all_result.count(self.TEST_RESULT_OK)
|
||||
return "%d/%d"% (test_loop_ok_result, test_loop_count)
|
||||
return "%d/%d" % (test_loop_ok_result, test_loop_count)
|
||||
|
||||
def shape_global_test_loop_result(self, test_all_result, waterfall_and_consolidate):
|
||||
""" Reformats list of results to simple string
|
||||
|
@ -1106,16 +1111,16 @@ class SingleTestRunner(object):
|
|||
Returns string
|
||||
"""
|
||||
result = None
|
||||
if re.search("HOST: Property '%s'"% property_name, line) is not None:
|
||||
property = re.search("HOST: Property '%s' = '([\w\d _]+)'"% property_name, line)
|
||||
if re.search("HOST: Property '%s'" % property_name, line) is not None:
|
||||
property = re.search("HOST: Property '%s' = '([\w\d _]+)'" % property_name, line)
|
||||
if property is not None and len(property.groups()) == 1:
|
||||
result = property.groups()[0]
|
||||
return result
|
||||
|
||||
cmd = ["python",
|
||||
'%s.py'% name,
|
||||
'%s.py' % name,
|
||||
'-d', disk,
|
||||
'-f', '"%s"'% image_path,
|
||||
'-f', '"%s"' % image_path,
|
||||
'-p', port,
|
||||
'-t', str(duration),
|
||||
'-C', str(program_cycle_s)]
|
||||
|
@ -1139,7 +1144,7 @@ class SingleTestRunner(object):
|
|||
|
||||
proc = Popen(cmd, stdout=PIPE, cwd=HOST_TESTS)
|
||||
obs = ProcessObserver(proc)
|
||||
update_once_flag = {} # Stores flags checking if some auto-parameter was already set
|
||||
update_once_flag = {} # Stores flags checking if some auto-parameter was already set
|
||||
line = ''
|
||||
output = []
|
||||
start_time = time()
|
||||
|
@ -1178,7 +1183,7 @@ class SingleTestRunner(object):
|
|||
else:
|
||||
line += c
|
||||
end_time = time()
|
||||
testcase_duration = end_time - start_time # Test case duration from reset to {end}
|
||||
testcase_duration = end_time - start_time # Test case duration from reset to {end}
|
||||
|
||||
c = get_char_from_queue(obs)
|
||||
|
||||
|
@ -1255,11 +1260,11 @@ def show_json_file_format_error(json_spec_filename, line, column):
|
|||
with open(json_spec_filename) as data_file:
|
||||
line_no = 1
|
||||
for json_line in data_file:
|
||||
if line_no + 5 >= line: # Print last few lines before error
|
||||
print('Line %d:\t'%line_no + json_line)
|
||||
if line_no + 5 >= line: # Print last few lines before error
|
||||
print('Line %d:\t' % line_no + json_line)
|
||||
if line_no == line:
|
||||
print('%s\t%s^' (' ' * len('Line %d:' % line_no),
|
||||
'-' * (column - 1)))
|
||||
print('%s\t%s^'(' ' * len('Line %d:' % line_no),
|
||||
'-' * (column - 1)))
|
||||
break
|
||||
line_no += 1
|
||||
|
||||
|
@ -1307,7 +1312,7 @@ def get_json_data_from_file(json_spec_filename, verbose=False):
|
|||
show_json_file_format_error(json_spec_filename, line, column)
|
||||
|
||||
except IOError as fileopen_error_msg:
|
||||
print('JSON file %s not opened. Reason: %s\n'%
|
||||
print('JSON file %s not opened. Reason: %s\n' %
|
||||
(json_spec_filename, fileopen_error_msg))
|
||||
if verbose and result:
|
||||
pp = pprint.PrettyPrinter(indent=4)
|
||||
|
@ -1380,7 +1385,7 @@ def print_test_configuration_from_json(json_data, join_delim=", "):
|
|||
target_supported_toolchains = get_target_supported_toolchains(target)
|
||||
if not target_supported_toolchains:
|
||||
target_supported_toolchains = []
|
||||
target_name = target if target in TARGET_MAP else "%s*"% target
|
||||
target_name = target if target in TARGET_MAP else "%s*" % target
|
||||
row = [target_name]
|
||||
toolchains = targets[target]
|
||||
|
||||
|
@ -1411,27 +1416,27 @@ def print_test_configuration_from_json(json_data, join_delim=", "):
|
|||
pt.add_row(row)
|
||||
|
||||
# generate result string
|
||||
result = pt.get_string() # Test specification table
|
||||
result = pt.get_string() # Test specification table
|
||||
if toolchain_conflicts or toolchain_path_conflicts:
|
||||
result += "\n"
|
||||
result += "Toolchain conflicts:\n"
|
||||
for target in toolchain_conflicts:
|
||||
if target not in TARGET_MAP:
|
||||
result += "\t* Target %s unknown\n"% (target)
|
||||
result += "\t* Target %s unknown\n" % (target)
|
||||
conflict_target_list = join_delim.join(toolchain_conflicts[target])
|
||||
sufix = 's' if len(toolchain_conflicts[target]) > 1 else ''
|
||||
result += "\t* Target %s does not support %s toolchain%s\n"% (target, conflict_target_list, sufix)
|
||||
result += "\t* Target %s does not support %s toolchain%s\n" % (target, conflict_target_list, sufix)
|
||||
|
||||
for toolchain in toolchain_path_conflicts:
|
||||
# Let's check toolchain configuration
|
||||
# Let's check toolchain configuration
|
||||
if toolchain in TOOLCHAIN_PATHS:
|
||||
toolchain_path = TOOLCHAIN_PATHS[toolchain]
|
||||
if not os.path.isdir(toolchain_path):
|
||||
result += "\t# Toolchain %s path not found: %s\n"% (toolchain, toolchain_path)
|
||||
result += "\t# Toolchain %s path not found: %s\n" % (toolchain, toolchain_path)
|
||||
return result
|
||||
|
||||
|
||||
def get_avail_tests_summary_table(cols=None, result_summary=True, join_delim=',',platform_filter=None):
|
||||
def get_avail_tests_summary_table(cols=None, result_summary=True, join_delim=',', platform_filter=None):
|
||||
""" Generates table summary with all test cases and additional test cases
|
||||
information using pretty print functionality. Allows test suite user to
|
||||
see test cases
|
||||
|
@ -1462,7 +1467,7 @@ def get_avail_tests_summary_table(cols=None, result_summary=True, join_delim=','
|
|||
|
||||
counter_all = 0
|
||||
counter_automated = 0
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
pt.padding_width = 1 # One space between column edges and contents (default)
|
||||
|
||||
for test_id in sorted(TEST_MAP.keys()):
|
||||
if platform_filter is not None:
|
||||
|
@ -1516,7 +1521,8 @@ def get_avail_tests_summary_table(cols=None, result_summary=True, join_delim=','
|
|||
pt.align['percent [%]'] = "r"
|
||||
for unique_id in unique_test_id:
|
||||
# print "\t\t%s: %d / %d" % (unique_id, counter_dict_test_id_types[unique_id], counter_dict_test_id_types_all[unique_id])
|
||||
percent_progress = round(100.0 * counter_dict_test_id_types[unique_id] / float(counter_dict_test_id_types_all[unique_id]), 1)
|
||||
percent_progress = round(
|
||||
100.0 * counter_dict_test_id_types[unique_id] / float(counter_dict_test_id_types_all[unique_id]), 1)
|
||||
str_progress = progress_bar(percent_progress, 75)
|
||||
row = [unique_id,
|
||||
counter_dict_test_id_types[unique_id],
|
||||
|
@ -1533,7 +1539,7 @@ def get_avail_tests_summary_table(cols=None, result_summary=True, join_delim=','
|
|||
def progress_bar(percent_progress, saturation=0):
|
||||
""" This function creates progress bar with optional simple saturation mark
|
||||
"""
|
||||
step = int(percent_progress / 2) # Scale by to (scale: 1 - 50)
|
||||
step = int(percent_progress / 2) # Scale by to (scale: 1 - 50)
|
||||
str_progress = '#' * step + '.' * int(50 - step)
|
||||
c = '!' if str_progress[38] == '.' else '|'
|
||||
if saturation > 0:
|
||||
|
@ -1573,26 +1579,32 @@ def singletest_in_cli_mode(single_test):
|
|||
if single_test.opts_report_html_file_name:
|
||||
# Export results in form of HTML report to separate file
|
||||
report_exporter = ReportExporter(ResultExporterType.HTML)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_html_file_name, test_suite_properties=test_suite_properties_ext)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_html_file_name,
|
||||
test_suite_properties=test_suite_properties_ext)
|
||||
if single_test.opts_report_junit_file_name:
|
||||
# Export results in form of JUnit XML report to separate file
|
||||
report_exporter = ReportExporter(ResultExporterType.JUNIT)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_junit_file_name, test_suite_properties=test_suite_properties_ext)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_junit_file_name,
|
||||
test_suite_properties=test_suite_properties_ext)
|
||||
if single_test.opts_report_text_file_name:
|
||||
# Export results in form of a text file
|
||||
report_exporter = ReportExporter(ResultExporterType.TEXT)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_text_file_name, test_suite_properties=test_suite_properties_ext)
|
||||
report_exporter.report_to_file(test_summary_ext, single_test.opts_report_text_file_name,
|
||||
test_suite_properties=test_suite_properties_ext)
|
||||
if single_test.opts_report_build_file_name:
|
||||
# Export build results as html report to sparate file
|
||||
report_exporter = ReportExporter(ResultExporterType.JUNIT, package="build")
|
||||
report_exporter.report_to_file(build_report, single_test.opts_report_build_file_name, test_suite_properties=build_properties)
|
||||
report_exporter.report_to_file(build_report, single_test.opts_report_build_file_name,
|
||||
test_suite_properties=build_properties)
|
||||
|
||||
# Returns True if no build failures of the test projects or their dependencies
|
||||
return status
|
||||
|
||||
|
||||
class TestLogger():
|
||||
""" Super-class for logging and printing ongoing events for test suite pass
|
||||
"""
|
||||
|
||||
def __init__(self, store_log=True):
|
||||
""" We can control if logger actually stores log in memory
|
||||
or just handled all log entries immediately
|
||||
|
@ -1608,18 +1620,18 @@ class TestLogger():
|
|||
ERROR='Error',
|
||||
EXCEPT='Exception')
|
||||
|
||||
self.LogToFileAttr = construct_enum(CREATE=1, # Create or overwrite existing log file
|
||||
APPEND=2) # Append to existing log file
|
||||
self.LogToFileAttr = construct_enum(CREATE=1, # Create or overwrite existing log file
|
||||
APPEND=2) # Append to existing log file
|
||||
|
||||
def log_line(self, LogType, log_line, timestamp=True, line_delim='\n'):
|
||||
""" Log one line of text
|
||||
"""
|
||||
log_timestamp = time()
|
||||
log_entry = {'log_type' : LogType,
|
||||
'log_timestamp' : log_timestamp,
|
||||
'log_line' : log_line,
|
||||
'_future' : None
|
||||
}
|
||||
log_entry = {'log_type': LogType,
|
||||
'log_timestamp': log_timestamp,
|
||||
'log_line': log_line,
|
||||
'_future': None
|
||||
}
|
||||
# Store log in memory
|
||||
if self.store_log:
|
||||
self.log.append(log_entry)
|
||||
|
@ -1629,18 +1641,20 @@ class TestLogger():
|
|||
class CLITestLogger(TestLogger):
|
||||
""" Logger used with CLI (Command line interface) test suite. Logs on screen and to file if needed
|
||||
"""
|
||||
|
||||
def __init__(self, store_log=True, file_name=None):
|
||||
TestLogger.__init__(self)
|
||||
self.log_file_name = file_name
|
||||
#self.TIMESTAMP_FORMAT = '%y-%m-%d %H:%M:%S' # Full date and time
|
||||
self.TIMESTAMP_FORMAT = '%H:%M:%S' # Time only
|
||||
# self.TIMESTAMP_FORMAT = '%y-%m-%d %H:%M:%S' # Full date and time
|
||||
self.TIMESTAMP_FORMAT = '%H:%M:%S' # Time only
|
||||
|
||||
def log_print(self, log_entry, timestamp=True):
|
||||
""" Prints on screen formatted log entry
|
||||
"""
|
||||
ts = log_entry['log_timestamp']
|
||||
timestamp_str = datetime.datetime.fromtimestamp(ts).strftime("[%s] "% self.TIMESTAMP_FORMAT) if timestamp else ''
|
||||
log_line_str = "%(log_type)s: %(log_line)s"% (log_entry)
|
||||
timestamp_str = datetime.datetime.fromtimestamp(ts).strftime(
|
||||
"[%s] " % self.TIMESTAMP_FORMAT) if timestamp else ''
|
||||
log_line_str = "%(log_type)s: %(log_line)s" % (log_entry)
|
||||
return timestamp_str + log_line_str
|
||||
|
||||
def log_line(self, LogType, log_line, timestamp=True, line_delim='\n'):
|
||||
|
@ -1678,7 +1692,7 @@ def detect_database_verbose(db_url):
|
|||
if result is not None:
|
||||
# Parsing passed
|
||||
(db_type, username, password, host, db_name) = result
|
||||
#print "DB type '%s', user name '%s', password '%s', host '%s', db name '%s'"% result
|
||||
# print "DB type '%s', user name '%s', password '%s', host '%s', db name '%s'"% result
|
||||
# Let's try to connect
|
||||
db_ = factory_db_logger(db_url)
|
||||
if db_ is not None:
|
||||
|
@ -1702,11 +1716,12 @@ def get_module_avail(module_name):
|
|||
"""
|
||||
return module_name in sys.modules.keys()
|
||||
|
||||
|
||||
def get_autodetected_MUTS_list(platform_name_filter=None):
|
||||
oldError = None
|
||||
if os.name == 'nt':
|
||||
# Disable Windows error box temporarily
|
||||
oldError = ctypes.windll.kernel32.SetErrorMode(1) #note that SEM_FAILCRITICALERRORS = 1
|
||||
oldError = ctypes.windll.kernel32.SetErrorMode(1) # note that SEM_FAILCRITICALERRORS = 1
|
||||
|
||||
mbeds = mbed_lstools.create()
|
||||
detect_muts_list = mbeds.list_mbeds()
|
||||
|
@ -1716,6 +1731,7 @@ def get_autodetected_MUTS_list(platform_name_filter=None):
|
|||
|
||||
return get_autodetected_MUTS(detect_muts_list, platform_name_filter=platform_name_filter)
|
||||
|
||||
|
||||
def get_autodetected_MUTS(mbeds_list, platform_name_filter=None):
|
||||
""" Function detects all connected to host mbed-enabled devices and generates artificial MUTS file.
|
||||
If function fails to auto-detect devices it will return empty dictionary.
|
||||
|
@ -1727,7 +1743,7 @@ def get_autodetected_MUTS(mbeds_list, platform_name_filter=None):
|
|||
@param mbeds_list list of mbeds captured from mbed_lstools
|
||||
@param platform_name You can filter 'platform_name' with list of filtered targets from 'platform_name_filter'
|
||||
"""
|
||||
result = {} # Should be in muts_all.json format
|
||||
result = {} # Should be in muts_all.json format
|
||||
# Align mbeds_list from mbed_lstools to MUT file format (JSON dictionary with muts)
|
||||
# mbeds_list = [{'platform_name': 'NUCLEO_F302R8', 'mount_point': 'E:', 'target_id': '07050200623B61125D5EF72A', 'serial_port': u'COM34'}]
|
||||
index = 1
|
||||
|
@ -1740,10 +1756,11 @@ def get_autodetected_MUTS(mbeds_list, platform_name_filter=None):
|
|||
# For mcu_unique - we are assigning 'platform_name_unique' value from mbedls output (if its existing)
|
||||
# if not we are creating our own unique value (last few chars from platform's target_id).
|
||||
m = {'mcu': mut['platform_name'],
|
||||
'mcu_unique' : mut['platform_name_unique'] if 'platform_name_unique' in mut else "%s[%s]" % (mut['platform_name'], mut['target_id'][-4:]),
|
||||
'mcu_unique': mut['platform_name_unique'] if 'platform_name_unique' in mut else "%s[%s]" % (
|
||||
mut['platform_name'], mut['target_id'][-4:]),
|
||||
'port': mut['serial_port'],
|
||||
'disk': mut['mount_point'],
|
||||
'peripherals': [] # No peripheral detection
|
||||
'peripherals': [] # No peripheral detection
|
||||
}
|
||||
if index not in result:
|
||||
result[index] = {}
|
||||
|
@ -1764,7 +1781,7 @@ def get_autodetected_TEST_SPEC(mbeds_list,
|
|||
use_supported_toolchains - if True add all supported toolchains to test_spec
|
||||
toolchain_filter - if [...list of toolchains...] add from all toolchains only those in filter to test_spec
|
||||
"""
|
||||
result = {'targets': {} }
|
||||
result = {'targets': {}}
|
||||
|
||||
for mut in mbeds_list:
|
||||
mcu = mut['mcu']
|
||||
|
@ -1822,7 +1839,7 @@ def get_default_test_options_parser():
|
|||
toolchain_list = list(TOOLCHAINS) + ["DEFAULT", "ALL"]
|
||||
parser.add_argument('--tc',
|
||||
dest='toolchains_filter',
|
||||
type=argparse_many(argparse_uppercase_type(toolchain_list, "toolchains")),
|
||||
type=argparse_many(argparse_uppercase_type(toolchain_list, "toolchains")),
|
||||
help="Toolchain filter for --auto argument. Use toolchains names separated by comma, 'default' or 'all' to select toolchains")
|
||||
|
||||
test_scopes = ','.join(["'%s'" % n for n in get_available_oper_test_scopes()])
|
||||
|
@ -1858,9 +1875,9 @@ def get_default_test_options_parser():
|
|||
help='Runs only test enumerated it this switch. Use comma to separate test case names')
|
||||
|
||||
parser.add_argument('-p', '--peripheral-by-names',
|
||||
dest='peripheral_by_names',
|
||||
type=argparse_many(str),
|
||||
help='Forces discovery of particular peripherals. Use comma to separate peripheral names')
|
||||
dest='peripheral_by_names',
|
||||
type=argparse_many(str),
|
||||
help='Forces discovery of particular peripherals. Use comma to separate peripheral names')
|
||||
|
||||
copy_methods = host_tests_plugins.get_plugin_caps('CopyMethod')
|
||||
copy_methods_str = "Plugin support: " + ', '.join(copy_methods)
|
||||
|
@ -1868,7 +1885,7 @@ def get_default_test_options_parser():
|
|||
parser.add_argument('-c', '--copy-method',
|
||||
dest='copy_method',
|
||||
type=argparse_uppercase_type(copy_methods, "flash method"),
|
||||
help="Select binary copy (flash) method. Default is Python's shutil.copy() method. %s"% copy_methods_str)
|
||||
help="Select binary copy (flash) method. Default is Python's shutil.copy() method. %s" % copy_methods_str)
|
||||
|
||||
reset_methods = host_tests_plugins.get_plugin_caps('ResetMethod')
|
||||
reset_methods_str = "Plugin support: " + ', '.join(reset_methods)
|
||||
|
@ -1877,7 +1894,7 @@ def get_default_test_options_parser():
|
|||
dest='mut_reset_type',
|
||||
default=None,
|
||||
type=argparse_uppercase_type(reset_methods, "reset method"),
|
||||
help='Extra reset method used to reset MUT by host test script. %s'% reset_methods_str)
|
||||
help='Extra reset method used to reset MUT by host test script. %s' % reset_methods_str)
|
||||
|
||||
parser.add_argument('-g', '--goanna-for-tests',
|
||||
dest='goanna_for_tests',
|
||||
|
@ -2041,18 +2058,20 @@ def get_default_test_options_parser():
|
|||
help="Depth level for static memory report")
|
||||
return parser
|
||||
|
||||
|
||||
def test_path_to_name(path, base):
|
||||
"""Change all slashes in a path into hyphens
|
||||
This creates a unique cross-platform test name based on the path
|
||||
This can eventually be overriden by a to-be-determined meta-data mechanism"""
|
||||
name_parts = []
|
||||
head, tail = os.path.split(relpath(path,base))
|
||||
head, tail = os.path.split(relpath(path, base))
|
||||
while (tail and tail != "."):
|
||||
name_parts.insert(0, tail)
|
||||
head, tail = os.path.split(head)
|
||||
|
||||
return "-".join(name_parts).lower()
|
||||
|
||||
|
||||
def get_test_config(config_name, target_name):
|
||||
"""Finds the path to a test configuration file
|
||||
config_name: path to a custom configuration file OR mbed OS interface "ethernet, wifi_odin, etc"
|
||||
|
@ -2066,13 +2085,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 +2110,39 @@ 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):
|
||||
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
|
||||
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))
|
||||
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
|
||||
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]
|
||||
|
||||
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
|
||||
for subdir in os.listdir(directory):
|
||||
d = join(directory, subdir)
|
||||
if not isdir(d):
|
||||
|
||||
# Apply common directories
|
||||
for pred, path in commons:
|
||||
|
@ -2131,6 +2153,7 @@ def find_tests(base_dir, target_name, toolchain_name, app_config=None):
|
|||
# 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"""
|
||||
|
@ -2147,6 +2170,7 @@ def print_tests(tests, format="list", sort=True):
|
|||
print("Unknown format '%s'" % format)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def norm_relative_path(path, start):
|
||||
"""This function will create a normalized, relative path. It mimics the
|
||||
python os.path.relpath function, but also normalizes a Windows-syle path
|
||||
|
@ -2235,7 +2259,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
|
||||
|
@ -2302,19 +2327,20 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
|
|||
|
||||
# Set the overall result to a failure if a build failure occurred
|
||||
if ('reason' in worker_result and
|
||||
not worker_result['reason'] and
|
||||
not isinstance(worker_result['reason'], NotSupportedException)):
|
||||
not worker_result['reason'] and
|
||||
not isinstance(worker_result['reason'], NotSupportedException)):
|
||||
result = False
|
||||
break
|
||||
|
||||
|
||||
# Adding binary path to test build result
|
||||
if ('result' in worker_result and
|
||||
worker_result['result'] and
|
||||
'bin_file' in worker_result):
|
||||
worker_result['result'] and
|
||||
'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
|
||||
|
@ -2358,3 +2384,5 @@ def test_spec_from_test_builds(test_builds):
|
|||
return {
|
||||
"builds": test_builds
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1117,4 +1117,4 @@ TOOLCHAIN_CLASSES = {
|
|||
u'IAR': IAR
|
||||
}
|
||||
|
||||
TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys())
|
||||
TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys())
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue