diff --git a/tests/Makefile b/tests/Makefile index c8ecf8b..67595f1 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,7 @@ PROGRAM=tests +EXTRA_COMPONENTS=extras/dhcpserver + PROGRAM_SRC_DIR = . ./cases # Add unity test framework headers & core source file diff --git a/tests/cases/03_byte_load_flash.c b/tests/cases/03_byte_load_flash.c index 4554e4c..fee464e 100644 --- a/tests/cases/03_byte_load_flash.c +++ b/tests/cases/03_byte_load_flash.c @@ -1,8 +1,9 @@ -/* Unit tests to verify the "unaligned load handler" in core/exception_vectors.S that allows us to - complete byte loads from unaligned memory, etc. - - Adapted from a test program in 'experiments' that did this. -*/ +/** + * Unit tests to verify the "unaligned load handler" in core/exception_vectors.S + * that allows us to complete byte loads from unaligned memory, etc. + * + * Adapted from a test program in 'experiments' that did this. + */ #include "testcase.h" #include "esp/rom.h" #include "esp/timer.h" diff --git a/tests/cases/04_wifi_basic.c b/tests/cases/04_wifi_basic.c new file mode 100644 index 0000000..382869d --- /dev/null +++ b/tests/cases/04_wifi_basic.c @@ -0,0 +1,181 @@ +/** + * This test verifies basic WiFi communication. + * + * Device A creates a WiFi access point and listens on port 23 for incomming + * connection. When incomming connection occurs it sends a string and waits + * for the response. + * + * Device B connects to a WiFi access point and opens TCP connection to + * device A on port 23. Then it receives a string and sends it back. + */ +#include + +#include +#include + +#include +#include "sdk_internal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "testcase.h" + +#define AP_SSID "esp-open-rtos-ap" +#define AP_PSK "esp-open-rtos" +#define SERVER "172.16.0.1" +#define PORT 23 +#define BUF_SIZE 128 + +DEFINE_TESTCASE(04_wifi_basic, DUAL) + +/********************************************************* + * WiFi AP part + *********************************************************/ + +static void server_task(void *pvParameters) +{ + char buf[BUF_SIZE]; + struct netconn *nc = netconn_new(NETCONN_TCP); + TEST_ASSERT_TRUE_MESSAGE(nc != 0, "Failed to allocate socket"); + + netconn_bind(nc, IP_ADDR_ANY, PORT); + netconn_listen(nc); + + struct netconn *client = NULL; + err_t err = netconn_accept(nc, &client); + TEST_ASSERT_TRUE_MESSAGE(err == ERR_OK, "Error accepting connection"); + + ip_addr_t client_addr; + uint16_t port_ignore; + netconn_peer(client, &client_addr, &port_ignore); + + snprintf(buf, sizeof(buf), "test ping\r\n"); + printf("Device A: send data: %s\n", buf); + netconn_write(client, buf, strlen(buf), NETCONN_COPY); + + struct pbuf *pb; + for (int i = 0; i < 10; i++) { + TEST_ASSERT_EQUAL_INT_MESSAGE(ERR_OK, netconn_recv_tcp_pbuf(client, &pb), + "Failed to receive data"); + if (pb->len > 0) { + memcpy(buf, pb->payload, pb->len); + buf[pb->len] = 0; + break; + } + vTaskDelay(100 / portTICK_RATE_MS); + } + TEST_ASSERT_TRUE_MESSAGE(pb->len > 0, "No data received"); + printf("Device A: received data: %s\n", buf); + TEST_ASSERT_FALSE_MESSAGE(strcmp((const char*)buf, "test pong\r\n"), + "Received wrong data"); + + netconn_delete(client); + + TEST_PASS(); +} + +static void a_04_wifi_basic(void) +{ + printf("Device A started\n"); + sdk_wifi_set_opmode(SOFTAP_MODE); + + struct ip_info ap_ip; + IP4_ADDR(&ap_ip.ip, 172, 16, 0, 1); + IP4_ADDR(&ap_ip.gw, 0, 0, 0, 0); + IP4_ADDR(&ap_ip.netmask, 255, 255, 0, 0); + sdk_wifi_set_ip_info(1, &ap_ip); + + struct sdk_softap_config ap_config = { + .ssid = AP_SSID, + .ssid_hidden = 0, + .channel = 3, + .ssid_len = strlen(AP_SSID), + .authmode = AUTH_WPA_WPA2_PSK, + .password = AP_PSK, + .max_connection = 3, + .beacon_interval = 100, + }; + sdk_wifi_softap_set_config(&ap_config); + + ip_addr_t first_client_ip; + IP4_ADDR(&first_client_ip, 172, 16, 0, 2); + dhcpserver_start(&first_client_ip, 4); + + xTaskCreate(server_task, (signed char *)"setver_task", 1024, NULL, 2, NULL); +} + + +/********************************************************* + * WiFi client part + *********************************************************/ + +static void connect_task(void *pvParameters) +{ + struct sockaddr_in serv_addr; + char buf[BUF_SIZE]; + + // wait for wifi connection + while (sdk_wifi_station_get_connect_status() != STATION_GOT_IP) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Waiting for connection to AP\n"); + } + + int s = socket(AF_INET, SOCK_STREAM, 0); + TEST_ASSERT_TRUE_MESSAGE(s >= 0, "Failed to allocate a socket"); + + bzero(&serv_addr, sizeof(serv_addr)); + serv_addr.sin_port = htons(PORT); + serv_addr.sin_family = AF_INET; + + TEST_ASSERT_TRUE_MESSAGE(inet_aton(SERVER, &serv_addr.sin_addr.s_addr), + "Failed to set IP address"); + + TEST_ASSERT_TRUE_MESSAGE( + connect(s, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0, + "Socket connection failed"); + + bzero(buf, BUF_SIZE); + + int r = 0; + for (int i = 0; i < 10; i++) { + r = read(s, buf, BUF_SIZE); + if (r > 0) { + break; + } + vTaskDelay(100 / portTICK_RATE_MS); + } + TEST_ASSERT_TRUE_MESSAGE(r > 0, "No data received"); + + printf("Device B: received data: %s\n", buf); + TEST_ASSERT_FALSE_MESSAGE(strcmp((const char*)buf, "test ping\r\n"), + "Received wrong data"); + + snprintf(buf, sizeof(buf), "test pong\r\n"); + printf("Device B: send data: %s\n", buf); + TEST_ASSERT_EQUAL_INT_MESSAGE(strlen(buf), write(s, buf, strlen(buf)), + "Error socket writing"); + + close(s); + + TEST_PASS(); +} + +static void b_04_wifi_basic(void) +{ + printf("Device B started\n"); + struct sdk_station_config config = { + .ssid = AP_SSID, + .password = AP_PSK, + }; + + sdk_wifi_set_opmode(STATION_MODE); + sdk_wifi_station_set_config(&config); + + xTaskCreate(&connect_task, (signed char *)"connect_task", 1024, NULL, 2, NULL); +} diff --git a/tests/include/testcase.h b/tests/include/testcase.h index 63e91de..11f59a4 100644 --- a/tests/include/testcase.h +++ b/tests/include/testcase.h @@ -41,7 +41,7 @@ void testcase_register(const testcase_t *testcase); #define DEFINE_TESTCASE(NAME, TYPE) \ static testcase_fn_t a_##NAME; \ static testcase_fn_t b_##NAME; \ - _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_##NAME, B_##NAME) + _DEFINE_TESTCASE_COMMON(NAME, TYPE, a_##NAME, b_##NAME) #define _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_FN, B_FN) \ diff --git a/tests/test_main.c b/tests/test_main.c index a93d6e1..aada715 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include /* Convert requirement enum to a string we can print */ static const char *get_requirements_name(const testcase_type_t arg) { @@ -30,7 +33,8 @@ void testcase_register(const testcase_t *testcase) testcases_alloc += 1; testcases = realloc(testcases, testcases_alloc * sizeof(testcase_t)); if(!testcases) { - printf("Failed to reallocate test case register length %d\n", testcases_alloc); + printf("Failed to reallocate test case register length %d\n", + testcases_alloc); testcases_count = 0; testcases_alloc = 0; } @@ -38,13 +42,14 @@ void testcase_register(const testcase_t *testcase) memcpy(&testcases[testcases_count++], testcase, sizeof(testcase_t)); } -void user_init(void) +static void test_task(void *pvParameters) { uart_set_baud(0, 115200); printf("esp-open-rtos test runner.\n"); printf("%d test cases are defined:\n\n", testcases_count); for(int i = 0; i < testcases_count; i++) { - printf("CASE %d = %s %s\n", i, testcases[i].name, get_requirements_name(testcases[i].type)); + printf("CASE %d = %s %s\n", i, testcases[i].name, + get_requirements_name(testcases[i].type)); } printf("Enter A or B then number of test case to run, ie A0.\n"); @@ -100,9 +105,11 @@ void user_init(void) testcases_alloc = 0; testcases_count = 0; - printf("\nRunning test case %d (%s %s) as instance %c \nDefinition at %s:%d\n***\n", case_idx, + printf("\nRunning test case %d (%s %s) as instance %c " + "\nDefinition at %s:%d\n***\n", case_idx, testcase.name, get_requirements_name(testcase.type), type, testcase.file, testcase.line); + Unity.CurrentTestName = testcase.name; Unity.TestFile = testcase.file; Unity.CurrentTestLineNumber = testcase.line; @@ -111,5 +118,13 @@ void user_init(void) testcase.a_fn(); else testcase.b_fn(); - TEST_FAIL_MESSAGE("\n\nTest initialisation routine returned without calling TEST_PASS. Buggy test?"); + /* TEST_FAIL_MESSAGE("\n\nTest initialisation routine returned" */ + /* " without calling TEST_PASS. Buggy test?"); */ +} + +void user_init(void) +{ + sdk_wifi_set_opmode(NULL_MODE); + test_task(0); + /* xTaskCreate(test_task, (signed char *)"test_task", 512, NULL, 2, NULL); */ } diff --git a/tests/test_runner.py b/tests/test_runner.py index 4acd017..f31d4f3 100755 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -7,11 +7,12 @@ import serial import threading import re import time -import traceback -SHORT_OUTPUT_TIMEOUT=0.25 # timeout for resetting and/or waiting for more lines of output -TESTCASE_TIMEOUT=10 -TESTRUNNER_BANNER="esp-open-rtos test runner." + +SHORT_OUTPUT_TIMEOUT = 0.25 # timeout for resetting and/or waiting for more lines of output +TESTCASE_TIMEOUT = 30 +TESTRUNNER_BANNER = "esp-open-rtos test runner." + def main(): global verbose @@ -24,6 +25,7 @@ def main(): flash_image(args.bport) env = TestEnvironment(args.aport, TestEnvironment.A) + env_b = None cases = env.get_testlist() if args.type != 'solo': env_b = TestEnvironment(args.bport, TestEnvironment.B) @@ -31,10 +33,16 @@ def main(): if cases != cases_b: raise TestRunnerError("Test cases on units A & B don't match") - counts = dict((status,0) for status in TestResult.STATUS_NAMES.keys()) + counts = dict((status, 0) for status in TestResult.STATUS_NAMES.keys()) failures = False for test in cases: - res = test.run(env) + if test.case_type == 'dual': + if env_b is None: + res = TestResult(TestResult.SKIPPED, 'Dual test case skipped') + else: + res = test.run(env, env_b) + else: + res = test.run(env) counts[res.status] += 1 failures = failures or res.is_failure() @@ -46,6 +54,7 @@ def main(): sys.exit(1 if failures else 0) + class TestCase(object): def __init__(self, index, name, case_type): self.name = name @@ -56,11 +65,11 @@ class TestCase(object): return "#%d: %s (%s)" % (self.index, self.name, self.case_type) def __eq__(self, other): - return (self.index == other.index - and self.name == other.name - and self.case_type == other.case_type) + return (self.index == other.index and + self.name == other.name and + self.case_type == other.case_type) - def run(self, env_a, env_b = None): + def run(self, env_a, env_b=None): """ Run the test represented by this instance, against the environment(s) passed in. @@ -71,7 +80,7 @@ class TestCase(object): mon_b = env_b.start_testcase(self) if env_b else None while True: if mon_a.get_result() and (mon_b is None or mon_b.get_result()): - break # all running test environments have finished + break # all running test environments have finished # or, in the case both are running, stop as soon as either environemnt shows a failure try: @@ -93,15 +102,16 @@ class TestCase(object): res = max(mon_a.get_result(), mon_b.get_result()) else: res = mon_a.get_result() - if not verbose: # finish the line after the ... + if not verbose: # finish the line after the ... print(TestResult.STATUS_NAMES[res.status]) if res.is_failure(): message = res.message - if "/" in res.message: # cut anything before the file name in the failure + if "/" in res.message: # cut anything before the file name in the failure message = message[message.index("/"):] print("FAILURE MESSAGE:\n%s\n" % message) return res + class TestResult(object): """ Class to wrap a test result code and a message """ # Test status flags, higher = more severe @@ -112,11 +122,11 @@ class TestResult(object): ERROR = 4 STATUS_NAMES = { - CANCELLED : "Cancelled", - SKIPPED : "Skipped", - PASSED : "Passed", - FAILED : "Failed", - ERROR : "Error" + CANCELLED: "Cancelled", + SKIPPED: "Skipped", + PASSED: "Passed", + FAILED: "Failed", + ERROR: "Error" } def __init__(self, status, message): @@ -126,10 +136,18 @@ class TestResult(object): def is_failure(self): return self.status >= TestResult.FAILED - def __cmp__(self, other): + def __qe__(self, other): if other is None: - return 1 - return self.status - other.status + return False + else: + return self.status == other.status + + def __lt__(self, other): + if other is None: + return False + else: + return self.status < other.status + class TestMonitor(object): """ Class to monitor a running test case in a separate thread, defer reporting of the result until it's done. @@ -163,7 +181,7 @@ class TestMonitor(object): while not self._cancelled and time.time() < start_time + TESTCASE_TIMEOUT: line = self._port.readline().decode("utf-8", "ignore") if line == "": - continue # timed out + continue # timed out self.output += "%s+%4.2fs %s" % (self._instance, time.time()-start_time, line) verbose_print(line.strip()) if line.endswith(":PASS\r\n"): @@ -183,6 +201,7 @@ class TestMonitor(object): finally: self._port.timeout = None + class TestEnvironment(object): A = "A" B = "B" @@ -211,7 +230,7 @@ class TestEnvironment(object): def collect_testcases(line): if line.startswith(">"): - return True # prompt means list of test cases is done, success + return True # prompt means list of test cases is done, success m = re.match(r"CASE (\d+) = (.+?) ([A-Z]+)", line) if m is not None: t = TestCase(int(m.group(1)), m.group(2), m.group(3).lower()) @@ -223,7 +242,9 @@ class TestEnvironment(object): return tests def start_testcase(self, case): - """ Starts the specified test instance and returns an TestMonitor reader thread instance to monitor the output """ + """ Starts the specified test instance and returns a TestMonitor reader thread instance + to monitor the output + """ # synchronously start the test case self.reset() if not self._port.wait_line(lambda line: line.startswith(">")): @@ -243,16 +264,18 @@ def get_testdir(): def flash_image(serial_port): - # Bit hacky: rather than calling esptool directly, just use the Makefile flash target - # with the correct ESPPORT argument + # Bit hacky: rather than calling esptool directly, + # just use the Makefile flash target with the correct ESPPORT argument env = dict(os.environ) env["ESPPORT"] = serial_port verbose_print("Building and flashing test image to %s..." % serial_port) try: stdout = sys.stdout if verbose else None - output = subprocess.run(["make","flash"], check=True, cwd=get_testdir(), stdout=stdout, stderr=subprocess.STDOUT, env=env) + subprocess.run(["make", "flash"], check=True, cwd=get_testdir(), + stdout=stdout, stderr=subprocess.STDOUT, env=env) except subprocess.CalledProcessError as e: - raise TestRunnerError("'make flash EPPORT=%s' failed with exit code %d" % (serial_port, e.returncode)) + raise TestRunnerError("'make flash EPPORT=%s' failed with exit code %d" % + (serial_port, e.returncode)) verbose_print("Flashing successful.") @@ -262,7 +285,7 @@ def parse_args(): parser.add_argument( '--type', '-t', help='Type of test hardware attached to serial ports A & (optionally) B', - choices=['solo','dual','eyore_test'], default='solo') + choices=['solo', 'dual', 'eyore_test'], default='solo') parser.add_argument( '--aport', '-a', @@ -296,11 +319,12 @@ class TestRunnerError(RuntimeError): def __init__(self, message): RuntimeError.__init__(self, message) + class TestSerialPort(serial.Serial): def __init__(self, *args, **kwargs): super(TestSerialPort, self).__init__(*args, **kwargs) - def wait_line(self, callback, timeout = SHORT_OUTPUT_TIMEOUT): + def wait_line(self, callback, timeout=SHORT_OUTPUT_TIMEOUT): """ Wait for the port to output a particular piece of line content, as judged by callback Callback called as 'callback(line)' and returns not-True if non-match otherwise can return any value. @@ -316,7 +340,7 @@ class TestSerialPort(serial.Serial): while not res: line = self.readline() if line == b"": - break # timed out + break # timed out line = line.decode("utf-8", "ignore").rstrip() res = callback(line) return res @@ -336,4 +360,3 @@ if __name__ == '__main__': except TestRunnerError as e: print(e) sys.exit(2) -