Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: features/frameworks/greentea-client/source/test_env.cpp
- Revision:
- 0:6c56fb4bc5f0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/features/frameworks/greentea-client/source/test_env.cpp Fri Nov 04 20:27:58 2016 +0000 @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2013-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 <ctype.h> +#include <cstdio> +#include <string.h> +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "greentea-client/greentea_serial.h" + + +/** + * Generic test suite transport protocol keys + */ +const char* GREENTEA_TEST_ENV_END = "end"; +const char* GREENTEA_TEST_ENV_EXIT = "__exit"; +const char* GREENTEA_TEST_ENV_SYNC = "__sync"; +const char* GREENTEA_TEST_ENV_TIMEOUT = "__timeout"; +const char* GREENTEA_TEST_ENV_HOST_TEST_NAME = "__host_test_name"; +const char* GREENTEA_TEST_ENV_HOST_TEST_VERSION = "__version"; + +/** + * Test suite success code strings + */ +const char* GREENTEA_TEST_ENV_SUCCESS = "success"; +const char* GREENTEA_TEST_ENV_FAILURE = "failure"; + +/** + * Test case transport protocol start/finish keys + */ +const char* GREENTEA_TEST_ENV_TESTCASE_NAME = "__testcase_name"; +const char* GREENTEA_TEST_ENV_TESTCASE_COUNT = "__testcase_count"; +const char* GREENTEA_TEST_ENV_TESTCASE_START = "__testcase_start"; +const char* GREENTEA_TEST_ENV_TESTCASE_FINISH = "__testcase_finish"; +const char* GREENTEA_TEST_ENV_TESTCASE_SUMMARY = "__testcase_summary"; +// Code Coverage (LCOV) transport protocol keys +const char* GREENTEA_TEST_ENV_LCOV_START = "__coverage_start"; + +/** + * Auxilary functions + */ +static void greentea_notify_timeout(const int); +static void greentea_notify_hosttest(const char *); +static void greentea_notify_completion(const int); +static void greentea_notify_version(); + +/** \brief Handshake with host and send setup data (timeout and host test name) + * \details This function will send preamble to master. + * After host test name is received master will invoke host test script + * and add hos test's callback handlers to main event loop + * This function is blocking. + */ +void GREENTEA_SETUP(const int timeout, const char *host_test_name) { + // Key-value protocol handshake function. Waits for {{__sync;...}} message + // Sync preamble: "{{__sync;0dad4a9d-59a3-4aec-810d-d5fb09d852c1}}" + // Example value of sync_uuid == "0dad4a9d-59a3-4aec-810d-d5fb09d852c1" + char _key[8] = {0}; + char _value[48] = {0}; + while (1) { + greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); + if (strcmp(_key, GREENTEA_TEST_ENV_SYNC) == 0) { + // Found correct __sunc message + greentea_send_kv(_key, _value); + break; + } + } + + greentea_notify_version(); + greentea_notify_timeout(timeout); + greentea_notify_hosttest(host_test_name); +} + +/** \brief Notify host (__exit message) side that test suite execution was complete + * \result Test suite result + * \details If __exit is not received by host side we will assume TIMEOUT + */ +void GREENTEA_TESTSUITE_RESULT(const int result) { + greentea_notify_completion(result); +} + +/** + * Test Case support + */ + +/** \brief Notify host side that test case started + * \details test_case_name Test case name + */ +void GREENTEA_TESTCASE_START(const char *test_case_name) { + greentea_send_kv(GREENTEA_TEST_ENV_TESTCASE_START, test_case_name); +} + +/** \brief Notify host side that test case finished + * \details test_case_name Test case name + * \details result Test case result (0 -OK, non zero...) + */ +void GREENTEA_TESTCASE_FINISH(const char *test_case_name, const size_t passes, const size_t failed) { + greentea_send_kv(GREENTEA_TEST_ENV_TESTCASE_FINISH, test_case_name, passes, failed); +} + +/** + ***************************************************************************** + * Auxilary functions and key-value protocol support + ***************************************************************************** + */ + + +/** + ***************************************************************************** + * LCOV support + ***************************************************************************** + */ +#ifdef MBED_CFG_DEBUG_OPTIONS_COVERAGE +extern "C" void __gcov_flush(void); +extern bool coverage_report; + +/** + * \brief Send code coverage (gcov/LCOV) notification to master + * + * Generates preamble of message sent to notify host about code coverage data dump. + * + * This function is used by mbedOS software + * (see: mbed-drivers/source/retarget.cpp file) to generate code coverage + * messages to host. When code coverage feature is turned on slave will + * print-out code coverage data in form of key-value protocol. + * Message with code coverage data will contain message name, path to code + * coverage output file host will touch and fill with code coverage binary + * payload. Coverage payload is encoded as stream of ASCII coded bytes ("%02X"). + * + * \param path to file with code coverage payload (set by gcov instrumentation) + * + */ +void greentea_notify_coverage_start(const char *path) { + printf("{{%s;%s;", GREENTEA_TEST_ENV_LCOV_START, path); +} + +/** + * \brief Sufix for code coverage message to master (closing statement) + * + * This function is used by mbedOS software + * (see: mbed-drivers/source/retarget.cpp file) to generate code coverage + * messages to host. When code coverage feature is turned on slave will + * print-out code coverage data in form of key-value protocol. + * Message with code coverage data will contain message name, path to code + * coverage output file host will touch and fill with code coverage binary + * payload. Coverage payload is encoded as stream of ASCII coded bytes ("%02X"). + * + * Companion function greentea_notify_coverage_start() defines code coverage message structure + * + */ +void greentea_notify_coverage_end() { + printf("}}" NL); +} + +#endif + +/** + ***************************************************************************** + * Key-value protocol support + ***************************************************************************** + */ + +/** + * \brief Write the preamble characters to the serial port + * + * This function writes the preamble "{{" which is required + * for key-value comunication between the target and the host. + * This uses a Rawserial object, greentea_serial, which provides + * a direct interface to the USBTX and USBRX serial pins and allows + * the direct writing of characters using the putc() method. + * This suite of functions are provided to allow for serial communication + * to the host from within a thread/ISR. + * + */ +inline void greentea_write_preamble() +{ + greentea_serial->putc('{'); + greentea_serial->putc('{'); +} + +/** + * \brief Write the postamble characters to the serial port + * + * This function writes the postamble "{{\n" which is required + * for key-value comunication between the target and the host. + * This uses a Rawserial object, greentea_serial, which provides + * a direct interface to the USBTX and USBRX serial pins and allows + * the direct writing of characters using the putc() method. + * This suite of functions are provided to allow for serial communication + * to the host from within a thread/ISR. + * + */ +inline void greentea_write_postamble() +{ + greentea_serial->putc('}'); + greentea_serial->putc('}'); + greentea_serial->putc('\n'); +} + +/** + * \brief Write a string to the serial port + * + * This function writes a '\0' terminated string from the target + * to the host. It writes directly to the serial port using the + * greentea_serial, Rawserial object. + * + * \param str - string value + * + */ +inline void greentea_write_string(const char *str) +{ + while (*str != '\0') { + greentea_serial->putc(*str); + str ++; + } +} + + +/** + * \brief Write an int to the serial port + * + * This function writes an integer value from the target + * to the host. The integer value is converted to a string and + * and then written character by character directly to the serial + * port using the greentea_serial, Rawserial object. + * sprintf() is used to convert the int to a string. Sprintf if + * inherently thread safe so can be used. + * + * \param val - integer value + * + */ +#define MAX_INT_STRING_LEN 15 +inline void greentea_write_int(const int val) +{ + char intval[MAX_INT_STRING_LEN]; + unsigned int i = 0; + sprintf(intval, "%d", val); + while (intval[i] != '\0') { + greentea_serial->putc(intval[i]); + i++; + } +} + +/** + * \brief Encapsulate and send key-value message from DUT to host + * + * This function uses underlying functions to write directly + * to the serial port, (USBTX). This allows KVs to be used + * from within interrupt context. + * + * \param key Message key (message/event name) + * \param value Message payload, string value + * + */ +void greentea_send_kv(const char *key, const char *val) { + if (key && val) { + greentea_write_preamble(); + greentea_write_string(key); + greentea_serial->putc(';'); + greentea_write_string(val); + greentea_write_postamble(); + } +} + +/** + * \brief Encapsulate and send key-value message from DUT to host + * + * This function uses underlying functions to write directly + * to the serial port, (USBTX). This allows KVs to be used + * from within interrupt context. + * Last value is an integer to avoid integer to string conversion + * made by the user. + * + * \param key Message key (message/event name) + * \param value Message payload, integer value + * + */ +void greentea_send_kv(const char *key, const int val) { + if (key) { + greentea_write_preamble(); + greentea_write_string(key); + greentea_serial->putc(';'); + greentea_write_int(val); + greentea_write_postamble(); + } +} + +/** + * \brief Encapsulate and send key-value-value message from DUT to host + * + * This function uses underlying functions to write directly + * to the serial port, (USBTX). This allows KVs to be used + * from within interrupt context. + * Last value is an integer to avoid integer to string conversion + * made by the user. + * + * \param key Message key (message/event name) + * \param value Message payload, string value + * \param result Send additional integer formatted data + * + */ +void greentea_send_kv(const char *key, const char *val, const int result) { + if (key) { + greentea_write_preamble(); + greentea_write_string(key); + greentea_serial->putc(';'); + greentea_write_string(val); + greentea_serial->putc(';'); + greentea_write_int(result); + greentea_write_postamble(); + + } +} + +/** + * \brief Encapsulate and send key-value-value-value message from DUT to host + * + * This function uses underlying functions to write directly + * to the serial port, (USBTX). This allows KVs to be used + * from within interrupt context. + * Last 2 values are integers to avoid integer to string conversion + * made by the user. + * + * Names of the parameters: this function is used to send test case + * name with number of passes and failures to host. But it can be used + * to send any key-value-value-value (string-string-integer-integer) + * set to host. + * + * \param key Message key (message/event name) + * \param value Message payload, string value + * \param passes Send additional integer formatted data + * \param failures Send additional integer formatted data + * + */ +void greentea_send_kv(const char *key, const char *val, const int passes, const int failures) { + if (key) { + greentea_write_preamble(); + greentea_write_string(key); + greentea_serial->putc(';'); + greentea_write_string(val); + greentea_serial->putc(';'); + greentea_write_int(passes); + greentea_serial->putc(';'); + greentea_write_int(failures); + greentea_write_postamble(); + } +} + +/** + * \brief Encapsulate and send key-value-value message from DUT to host + * + * This function uses underlying functions to write directly + * to the serial port, (USBTX). This allows key-value-value to be used + * from within interrupt context. + * Both values are integers to avoid integer to string conversion + * made by the user. + * + * Names of the parameters: this function is used to send number + * of passes and failures to host. But it can be used to send any + * key-value-value (string-integer-integer) message to host. + * + * \param key Message key (message/event name) + * \param value Message payload, integer value + * \param passes Send additional integer formatted data + * \param failures Send additional integer formatted data + * + */ +void greentea_send_kv(const char *key, const int passes, const int failures) { + if (key) { + greentea_write_preamble(); + greentea_write_string(key); + greentea_serial->putc(';'); + greentea_write_int(passes); + greentea_serial->putc(';'); + greentea_write_int(failures); + greentea_write_postamble(); + } +} + +/** + * \brief Send message with timeout to master in seconds + * + * GREENTEA_TEST_ENV_TIMEOUT message is part of preamble + * sent from DUT to host during synchronisation (beginning of test + * suite execution). + * + * Notification about total test suite timeout. Timeout is measured + * from the moment of GREENTEA_TEST_ENV_TIMEOUT reception by host. + * If timeout is reached host (and host test) will be stopped and + * control will return to Greentea. + * + * \param timeout Test suite timeout in seconds + * + */ +static void greentea_notify_timeout(const int timeout) { + greentea_send_kv(GREENTEA_TEST_ENV_TIMEOUT, timeout); +} + +/** + * \brief Send host test name to master + * + * GREENTEA_TEST_ENV_HOST_TEST_NAME message is part of preamble + * sent from DUT to host during synchronisation (beginning of test + * suite execution). + * + * Host test Python script implements host side callbacks + * for key-value events sent from DUT to host. Host test's + * callbacks are registered after GREENTEA_TEST_ENV_HOST_TEST_NAME + * message reaches host. + * + * \param host_test_name Host test name, host test will be loaded by mbedhtrun + */ +static void greentea_notify_hosttest(const char *host_test_name) { + greentea_send_kv(GREENTEA_TEST_ENV_HOST_TEST_NAME, host_test_name); +} + +/** + * \brief Send to master information that test suite finished its execution + * + * GREENTEA_TEST_ENV_END and GREENTEA_TEST_ENV_EXIT messages + * are sent just before test suite execution finishes (noting + * else to do). You can place it just before you return from your + * main() function. + * + * Code coverage: If MEBD_CFG_DEBUG_OPTIONS_COVERAGE is set in the + * project via build configuration function will output series + * of code coverage messages GREENTEA_TEST_ENV_LCOV_START with code + * coverage binary data. This data is captured by Greentea and can + * be used to generate LCOV reports. + * + * \param result Test suite result from DUT (0 - FAIl, !0 - SUCCESS) + * + */ +static void greentea_notify_completion(const int result) { + const char *val = result ? GREENTEA_TEST_ENV_SUCCESS : GREENTEA_TEST_ENV_FAILURE; +#ifdef MBED_CFG_DEBUG_OPTIONS_COVERAGE + coverage_report = true; + __gcov_flush(); + coverage_report = false; +#endif + greentea_send_kv(GREENTEA_TEST_ENV_END, val); + greentea_send_kv(GREENTEA_TEST_ENV_EXIT, 0); +} + +/** + * \brief Send to master greentea-client version + */ +static void greentea_notify_version() { + greentea_send_kv(GREENTEA_TEST_ENV_HOST_TEST_VERSION, MBED_GREENTEA_CLIENT_VERSION_STRING); +} + +/** + ***************************************************************************** + * Parse engine for KV values which replaces scanf + ***************************************************************************** + * + * Example usage: + * + * char key[10]; + * char value[48]; + * + * greentea_parse_kv(key, value, 10, 48); + * greentea_parse_kv(key, value, 10, 48); + * + */ + + +static int gettok(char *, const int); +static int getNextToken(char *, const int); +static int HandleKV(char *, char *, const int, const int); +static int isstring(int); +static int _get_char(); + +/** + * \brief Current token of key-value protocol's tokenizer + */ +static int CurTok = 0; + +/** + * \enum Token enumeration for key-value protocol tokenizer + * + * This enum is used by key-value protocol tokenizer + * to detect parts of protocol in stream. + * + * tok_eof ::= EOF (end of file) + * tok_open ::= "{{" + * tok_close ::= "}}" + * tok_semicolon ::= ";" + * tok_string ::= [a-zA-Z0-9_-!@#$%^&*()]+ // See isstring() function + * + */ +enum Token { + tok_eof = -1, + tok_open = -2, + tok_close = -3, + tok_semicolon = -4, + tok_string = -5 +}; + +/** + * \brief Read character from stream of data + * + * Closure for default "get character" function. + * This function is used to read characters from the stream + * (default is serial port RX). Key-value protocol tokenizer + * will build stream of tokes used by key-value protocol to + * detect valid messages. + * + * If EOF is received parser finishes parsing and stops. In + * situation where we have serial port stream of data parsing + * goes forever. + * + * \return Next character from the stream or EOF if stream has ended. + * + */ +static int _get_char() { + return getchar(); +} + +/** + * \brief parse input string for key-value pairs: {{key;value}} + * This function should replace scanf() used to + * check for incoming messages from master. All data + * parsed and rejected is discarded. + * + * \param out_key Ouput data with key + * \param out_value Ouput data with value + * \param out_key_size out_key total size + * \param out_value_size out_value total data + * + * success != 0 when key-value pair was found + * success == 0 when end of the stream was found + * + */ +int greentea_parse_kv(char *out_key, + char *out_value, + const int out_key_size, + const int out_value_size) { + getNextToken(0, 0); + while (1) { + switch (CurTok) { + case tok_eof: + return 0; + + case tok_open: + if (HandleKV(out_key, out_value, out_key_size, out_value_size)) { + // We've found {{ KEY ; VALUE }} expression + return 1; + } + break; + + default: + // Load next token and pray... + getNextToken(0, 0); + break; + } + } + return 0; +} + +/** + * \brief Get next token from stream + * + * Key-value TOKENIZER feature + * + * This function is used by key-value parser determine + * if key-value message is embedded in stream data. + * + * \param str Output parameters to store token string value + * \param str_size Size of 'str' parameter in bytes (characters) + * + */ +static int getNextToken(char *str, const int str_size) { + return CurTok = gettok(str, str_size); +} + +/** + * \brief Check if character is punctuation character + * + * Auxilary key-value TOKENIZER function + * + * Defines if character is in subset of allowed punctuation + * characters which can be part of a key or value string. + * Not allowed characters are: ";{}" + * + * \param c Input character to check + * \return Return 1 if character is allowed punctuation character, otherwise return false + * + */ +static int ispunctuation(int c) { + static const char punctuation[] = "_-!@#$%^&*()=+:<>,./?\\\"'"; // No ";{}" + for (size_t i=0; i< sizeof(punctuation); ++i) { + if (c == punctuation[i]) { + return 1; + } + } + return 0; +} + +/** + * \brief Check if character is string token character + * + * Auxilary key-value TOKENIZER function + * + * Defines if character is in subset of allowed string + * token characters. + * String defines set of characters which can be a key or value string. + * + * Allowed subset includes: + * - Alphanumerical characters + * - Digits + * - White spaces and + * - subset of punctuation characters. + * + * \param c Input character to check + * \return Return 1 if character is allowed punctuation character, otherwise return false + * + */ +static int isstring(int c) { + return (isalpha(c) || + isdigit(c) || + isspace(c) || + ispunctuation(c)); +} + +/** + * \brief TOKENIZER of key-value protocol + * + * Actual key-value TOKENIZER engine + * + * TOKENIZER defines #Token enum to map recognized tokens to integer values. + * + * <TOK_EOF> ::= EOF (end of file) + * <TOK_OPEN> ::= "{{" + * <TOK_CLOSE> ::= "}}" + * <TOK_SEMICOLON> ::= ";" + * <TOK_STRING> ::= [a-zA-Z0-9_-!@#$%^&*()]+ // See isstring() function * + * + * \param out_str Output string with parsed token (string) + * \param str_size Size of str buffer we can use + * + * \return Return #Token enum value used by parser to check for key-value occurrences + * + */ +static int gettok(char *out_str, const int str_size) { + static int LastChar = '!'; + static int str_idx = 0; + + // whitespace ::= + while (isspace(LastChar)) { + LastChar = _get_char(); + } + + // string ::= [a-zA-Z0-9_-!@#$%^&*()]+ + if (isstring(LastChar)) { + str_idx = 0; + if (out_str && str_idx < str_size - 1) { + out_str[str_idx++] = LastChar; + } + + while (isstring((LastChar = _get_char()))) + if (out_str && str_idx < str_size - 1) { + out_str[str_idx++] = LastChar; + } + if (out_str && str_idx < str_size) { + out_str[str_idx] = '\0'; + } + + return tok_string; + } + + // semicolon ::= ';' + if (LastChar == ';') { + LastChar = _get_char(); + return tok_semicolon; + } + + // open ::= '{{' + if (LastChar == '{') { + LastChar = _get_char(); + if (LastChar == '{') { + LastChar = _get_char(); + return tok_open; + } + } + + // close ::= '}' + if (LastChar == '}') { + LastChar = _get_char(); + if (LastChar == '}') { + //LastChar = _get_char(); + return tok_close; + } + } + + if (LastChar == EOF) + return tok_eof; + + // Otherwise, just return the character as its ascii value. + int ThisChar = LastChar; + LastChar = _get_char(); + return ThisChar; +} + +/** + * \brief Key-value parser + * + * Key-value message grammar + * + * <MESSAGE>: <TOK_OPEN> <TOK_STRING> <TOK_SEMICOLON> <TOK_STRING> <TOK_CLOSE> + * + * Examples: + * message: "{{__timeout; 1000}}" + * "{{__sync; 12345678-1234-5678-1234-567812345678}}" + * + * \param out_key Output buffer to store key string value + * \param out_value Output buffer to store value string value + * \param out_key_size Buffer 'out_key' buffer size + * \param out_value_size Buffer 'out_value_size' buffer size + * \return Returns 1 if key-value message was parsed successfully in stream of tokens from tokenizer + * + */ +static int HandleKV(char *out_key, + char *out_value, + const int out_key_size, + const int out_value_size) { + // We already started with <open> + if (getNextToken(out_key, out_key_size) == tok_string) { + if (getNextToken(0, 0) == tok_semicolon) { + if (getNextToken(out_value, out_value_size) == tok_string) { + if (getNextToken(0, 0) == tok_close) { + // <open> <string> <semicolon> <string> <close> + // Found "{{KEY;VALUE}}" expression + return 1; + } + } + } + } + getNextToken(0, 0); + return 0; +}