Icetea support

pull/7745/head
Olli-Pekka Puolitaival 2018-01-23 12:26:14 +02:00
parent 2e869df296
commit e27a26eb9d
60 changed files with 7763 additions and 236 deletions

3
.gitignore vendored
View File

@ -88,3 +88,6 @@ tags
.vscode/
features/FEATURE_BLE/targets/TARGET_CORDIO/stack_backup/
.pytest_cache
log

2
.mbedignore Normal file
View File

@ -0,0 +1,2 @@
test/*
source/ns_list_internal/*

0
.yotta_ignore Normal file
View File

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
SRCS := $(wildcard source/*.c)
LIB := libCmdline.a
EXPORT_HEADERS := mbed-client-cli
include ../exported_rules.mk

105
README.md
View File

@ -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
TEST_APPS/__init__.py Normal file
View File

View File

@ -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;
}

View File

@ -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";
}
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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"]
}
}
}

View File

@ -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;
}

View File

@ -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

View File

View File

@ -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

View File

@ -0,0 +1,8 @@
#
# Copyright (c) 2018, Arm Limited and affiliates.
#
from ip_test_parsers import IpTestParsers
plugins_to_load = {
"ip_test_parsers": IpTestParsers
}

115
TEST_APPS/readme.md Normal file
View File

@ -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.

View File

View File

View File

@ -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

View File

@ -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")

View File

@ -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"])

View File

@ -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")

View File

@ -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))

View File

@ -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")

View File

@ -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"
}
}
}
}

View File

@ -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_*/

33
module.json Normal file
View File

@ -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"
}
}
}

14
source/CMakeLists.txt Normal file
View File

@ -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()

1829
source/ns_cmdline.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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_ */

View File

@ -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
target.json Normal file
View File

27
test/CMakeLists.txt Normal file
View File

@ -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()

785
test/Test.cpp Normal file
View File

@ -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);
}

27
test_suite.json Normal file
View File

@ -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"
}
]
}

View File

@ -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=(',', ': '))

View File

@ -69,6 +69,7 @@ LEGACY_IGNORE_DIRS = set([
# Tests, here for simplicity
'TESTS',
'TEST_APPS',
])
LEGACY_TOOLCHAIN_NAMES = {
'ARM_STD':'ARM',

335
tools/run_icetea.py Normal file
View File

@ -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)

View File

@ -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
tools/test/__init__.py Normal file
View File

1
tools/test/run_icetea/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test_suite.json

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
This folder contains hardware test data for icetea integration

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

View File

@ -0,0 +1,3 @@
{
"builds": []
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -1117,4 +1117,4 @@ TOOLCHAIN_CLASSES = {
u'IAR': IAR
}
TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys())
TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys())

View File

@ -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)