Unit testing for esp-open-rtos (#253)
* Get testing system by projectgus working with master HEAD * Fix running dual test. Add basic wifi test * Moved spiff test to a test case. Reset retries in test runner * Add timers test case * test_runner: List test cases and run individual test cases * Add README for tests * Update README.md * Code clean-up * Python3.4 support. README.md update
This commit is contained in:
		
							parent
							
								
									dda384f3a1
								
							
						
					
					
						commit
						7c702d7f09
					
				
					 19 changed files with 1562 additions and 45 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -10,3 +10,4 @@ firmware
 | 
			
		|||
local.mk
 | 
			
		||||
local.h
 | 
			
		||||
screenlog.*
 | 
			
		||||
*.swp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -13,6 +13,9 @@
 | 
			
		|||
[submodule "extras/spiffs/spiffs"]
 | 
			
		||||
	path = extras/spiffs/spiffs
 | 
			
		||||
	url = https://github.com/pellepl/spiffs.git
 | 
			
		||||
[submodule "examples/posix_fs/fs-test"]
 | 
			
		||||
	path = examples/posix_fs/fs-test
 | 
			
		||||
[submodule "tests/unity"]
 | 
			
		||||
	path = tests/unity
 | 
			
		||||
	url = https://github.com/ThrowTheSwitch/Unity.git
 | 
			
		||||
[submodule "tests/fs-test"]
 | 
			
		||||
	path = tests/fs-test
 | 
			
		||||
	url = https://github.com/sheinz/fs-test
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
PROGRAM=posix_fs_example
 | 
			
		||||
PROGRAM_EXTRA_SRC_FILES=./fs-test/fs_test.c
 | 
			
		||||
 | 
			
		||||
EXTRA_COMPONENTS = extras/spiffs
 | 
			
		||||
FLASH_SIZE = 32
 | 
			
		||||
 | 
			
		||||
# spiffs configuration
 | 
			
		||||
SPIFFS_BASE_ADDR = 0x200000
 | 
			
		||||
SPIFFS_SIZE = 0x100000
 | 
			
		||||
 | 
			
		||||
include ../../common.mk
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
# POSIX file access example
 | 
			
		||||
 | 
			
		||||
This example runs several file system tests on ESP8266.
 | 
			
		||||
It uses fs-test library to perform file operations test. fs-test library uses
 | 
			
		||||
only POSIX file functions so can be run on host system as well.
 | 
			
		||||
 | 
			
		||||
Currently included tests:
 | 
			
		||||
 * File system load test. Perform multiple file operations in random order.
 | 
			
		||||
 * File system speed test. Measures files read/write speed.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
Subproject commit 2ad547adc5f725594b3c6752f036ff4401b221fc
 | 
			
		||||
							
								
								
									
										27
									
								
								tests/Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tests/Makefile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
PROGRAM=tests
 | 
			
		||||
 | 
			
		||||
EXTRA_COMPONENTS=extras/dhcpserver extras/spiffs
 | 
			
		||||
 | 
			
		||||
PROGRAM_SRC_DIR = . ./cases
 | 
			
		||||
 | 
			
		||||
FLASH_SIZE = 32
 | 
			
		||||
 | 
			
		||||
# spiffs configuration
 | 
			
		||||
SPIFFS_BASE_ADDR = 0x200000
 | 
			
		||||
SPIFFS_SIZE = 0x100000
 | 
			
		||||
 | 
			
		||||
# Add unity test framework headers & core source file
 | 
			
		||||
PROGRAM_INC_DIR = ./unity/src ./fs-test
 | 
			
		||||
PROGRAM_EXTRA_SRC_FILES = ./unity/src/unity.c ./fs-test/fs_test.c
 | 
			
		||||
 | 
			
		||||
TESTCASE_SRC_FILES = $(wildcard $(PROGRAM_DIR)cases/*.c)
 | 
			
		||||
 | 
			
		||||
# Do not include source files into a static library because when adding this
 | 
			
		||||
# library with '--whole-archive' linker gives error that archive contains
 | 
			
		||||
# unknown objects (source files)
 | 
			
		||||
INCLUDE_SRC_IN_AR = 0
 | 
			
		||||
 | 
			
		||||
# Link every object in the 'program' archive, to pick up constructor functions for test cases
 | 
			
		||||
EXTRA_LDFLAGS = -Wl,--whole-archive $(BUILD_DIR)program.a -Wl,--no-whole-archive
 | 
			
		||||
 | 
			
		||||
include ../common.mk
 | 
			
		||||
							
								
								
									
										66
									
								
								tests/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								tests/README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
# esp-open-rtos tests
 | 
			
		||||
 | 
			
		||||
Testing is based on [Unity](https://github.com/ThrowTheSwitch/Unity)
 | 
			
		||||
C testing framework.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
* Single device test case.
 | 
			
		||||
* Dual devices test cases. Run test case on two ESP8266 modules simultaneously.
 | 
			
		||||
* Run only specified test cases.
 | 
			
		||||
* List available test cases on a device.
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
There's a test runner script `test_runner.py` written in Python3 that runs
 | 
			
		||||
test cases on ESP8266 connected to a host.
 | 
			
		||||
 | 
			
		||||
### Requirements and dependencies
 | 
			
		||||
 | 
			
		||||
* Python3 version > 3.4 `sudo apt-get install python3 python3-pip`
 | 
			
		||||
* pyserial `sudo pip3 install pyserial`
 | 
			
		||||
* ESP8266 board with reset to boot mode support
 | 
			
		||||
* Two ESP8266 for dual mode test cases
 | 
			
		||||
 | 
			
		||||
Test runner is heavily relying on device reset using DTR and RTS signals.
 | 
			
		||||
Popular ESP8266 boards such as **NodeMcu** and **Wemos D1** support device
 | 
			
		||||
reset into flash mode.
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 | 
			
		||||
`--type` or `-t` - Type of test case to run. Can be 'solo' or 'dual'.
 | 
			
		||||
If not specified 'solo' test will be run.
 | 
			
		||||
 | 
			
		||||
`--aport` or `-a` - Serial port for device A.
 | 
			
		||||
If not specified device `/dev/ttyUSB0` is used.
 | 
			
		||||
 | 
			
		||||
`--bport` or `-b` - Serial port for device B.
 | 
			
		||||
If not specified device `/dev/ttyUSB1` is used.
 | 
			
		||||
 | 
			
		||||
`--no-flash` or `-n` - Do not flash the test firmware before running tests.
 | 
			
		||||
 | 
			
		||||
`--list` or `-l` - Display list of the available test cases on the device.
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
 | 
			
		||||
Build test firmware, flash it using serial device `/dev/tty.wchusbserial1410`
 | 
			
		||||
and run only *solo* test cases:
 | 
			
		||||
 | 
			
		||||
`./test_runner.py -a /dev/tty.wchusbserial1410`
 | 
			
		||||
 | 
			
		||||
Build test firmware. Flash both devices as `-t dual` is specified. And run both
 | 
			
		||||
*solo* and *dual* test cases:
 | 
			
		||||
 | 
			
		||||
`./test_runner.py -a /dev/tty.wchusbserial1410 -b /dev/tty.wchusbserial1420 -t dual`
 | 
			
		||||
 | 
			
		||||
Do not flash the firmware, only display available test cases on the device:
 | 
			
		||||
 | 
			
		||||
`./test_runner.py -a /dev/tty.wchusbserial1410 -n -l`
 | 
			
		||||
 | 
			
		||||
Do not flash the firmware and run only 2 and 4 test cases:
 | 
			
		||||
 | 
			
		||||
`./test_runner.py -a /dev/tty.wchusbserial1410 -n 2 4`
 | 
			
		||||
 | 
			
		||||
## References
 | 
			
		||||
 | 
			
		||||
[Unity](https://github.com/ThrowTheSwitch/Unity) - Simple Unit Testing for C
 | 
			
		||||
							
								
								
									
										71
									
								
								tests/cases/01_scheduler.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								tests/cases/01_scheduler.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
#include "testcase.h"
 | 
			
		||||
#include <FreeRTOS.h>
 | 
			
		||||
#include <task.h>
 | 
			
		||||
#include <esp/uart.h>
 | 
			
		||||
 | 
			
		||||
/* Basic test cases to validate the FreeRTOS scheduler works */
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(01_scheduler_basic)
 | 
			
		||||
DEFINE_SOLO_TESTCASE(01_scheduler_priorities)
 | 
			
		||||
 | 
			
		||||
void set_variable(void *pvParameters)
 | 
			
		||||
{
 | 
			
		||||
    bool *as_bool = (bool *)pvParameters;
 | 
			
		||||
    *as_bool = true;
 | 
			
		||||
    /* deliberately a busywait at the end, not vTaskSuspend, to test priorities */
 | 
			
		||||
    while(1) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Really simple - do created tasks run? */
 | 
			
		||||
static void a_01_scheduler_basic()
 | 
			
		||||
{
 | 
			
		||||
    volatile bool a = false, b = false, c = false;
 | 
			
		||||
    printf("top of scheduler...\n");
 | 
			
		||||
    uart_flush_txfifo(0);
 | 
			
		||||
 | 
			
		||||
    xTaskCreate(set_variable, "set_a", 128, (void *)&a, tskIDLE_PRIORITY, NULL);
 | 
			
		||||
    xTaskCreate(set_variable, "set_b", 128, (void *)&b, tskIDLE_PRIORITY, NULL);
 | 
			
		||||
    xTaskCreate(set_variable, "set_c", 128, (void *)&c, tskIDLE_PRIORITY, NULL);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(a, "task set_a shouldn't run yet");
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(b, "task set_b shouldn't run yet");
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(c, "task set_c shouldn't run yet");
 | 
			
		||||
 | 
			
		||||
    vTaskDelay(5);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(a, "task set_a should have run");
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(b, "task set_b should have run");
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(c, "task set_c should have run");
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Verify that a high-priority task will starve a lower priority task */
 | 
			
		||||
static void a_01_scheduler_priorities()
 | 
			
		||||
{
 | 
			
		||||
    /* Increase priority of the init task to make sure it always takes priority */
 | 
			
		||||
    vTaskPrioritySet(xTaskGetCurrentTaskHandle(), tskIDLE_PRIORITY+4);
 | 
			
		||||
 | 
			
		||||
    bool lower = false, higher = false;
 | 
			
		||||
    TaskHandle_t task_lower, task_higher;
 | 
			
		||||
 | 
			
		||||
    xTaskCreate(set_variable, "high_prio", 128, (void *)&higher, tskIDLE_PRIORITY+1, &task_higher);
 | 
			
		||||
    xTaskCreate(set_variable, "low_prio", 128, (void *)&lower, tskIDLE_PRIORITY, &task_lower);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(higher, "higher prio task should not have run yet");
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(lower, "lower prio task should not have run yet");
 | 
			
		||||
 | 
			
		||||
    vTaskDelay(2);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(higher, "higher prio task should have run");
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(lower, "lower prio task should not have run");
 | 
			
		||||
 | 
			
		||||
    /* Bump lower priority task over higher priority task */
 | 
			
		||||
    vTaskPrioritySet(task_lower, tskIDLE_PRIORITY+2);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_FALSE_MESSAGE(lower, "lower prio task should still not have run yet");
 | 
			
		||||
 | 
			
		||||
    vTaskDelay(1);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(lower, "lower prio task should have run");
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								tests/cases/02_heap.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tests/cases/02_heap.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
#include "testcase.h"
 | 
			
		||||
#include <malloc.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <FreeRTOS.h>
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(02_heap_simple)
 | 
			
		||||
DEFINE_SOLO_TESTCASE(02_heap_full)
 | 
			
		||||
 | 
			
		||||
/* Simple heap accounting tests */
 | 
			
		||||
static void a_02_heap_simple()
 | 
			
		||||
{
 | 
			
		||||
    struct mallinfo info = mallinfo();
 | 
			
		||||
    printf("'arena' allocation size %d bytes\n", info.arena);
 | 
			
		||||
    /* This is really a sanity check, if the "arena" size shrinks then
 | 
			
		||||
       this is a good thing and we can update the test. If it grows
 | 
			
		||||
       then we can also update the test, but we need a good reason. */
 | 
			
		||||
    TEST_ASSERT_INT_WITHIN_MESSAGE(1000, 15000, info.arena, "Initial allocated heap should be approximately 15kB. SEE COMMENT.");
 | 
			
		||||
 | 
			
		||||
    uint32_t freeheap = xPortGetFreeHeapSize();
 | 
			
		||||
    printf("xPortGetFreeHeapSize = %d bytes\n", freeheap);
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(freeheap > 20000, "Should be at least 20kB free.");
 | 
			
		||||
 | 
			
		||||
    uint8_t *buf = malloc(8192);
 | 
			
		||||
    /* <-- have to do something with buf or gcc helpfully optimises it out! */
 | 
			
		||||
    memset(buf, 0xEE, 8192);
 | 
			
		||||
    uint32_t after = xPortGetFreeHeapSize();
 | 
			
		||||
    struct mallinfo after_info = mallinfo();
 | 
			
		||||
    printf("after arena size = %d bytes\n", after_info.arena);
 | 
			
		||||
    printf("after xPortGetFreeHeapSize = %d bytes\n", after);
 | 
			
		||||
    TEST_ASSERT_UINT32_WITHIN_MESSAGE(100, info.arena+8192, after_info.arena, "Allocated heap 'after' size should be 8kB more than before");
 | 
			
		||||
    TEST_ASSERT_UINT32_WITHIN_MESSAGE(100, freeheap-8192, after, "Free heap size should be 8kB less than before");
 | 
			
		||||
 | 
			
		||||
    free(buf);
 | 
			
		||||
    after = xPortGetFreeHeapSize();
 | 
			
		||||
    printf("after freeing xPortGetFreeHeapSize = %d bytes\n", after);
 | 
			
		||||
    TEST_ASSERT_UINT32_WITHIN_MESSAGE(100, freeheap, after, "Free heap size after freeing buffer should be close to initial");
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Ensure malloc behaves when out of memory */
 | 
			
		||||
static void a_02_heap_full()
 | 
			
		||||
{
 | 
			
		||||
    void *x = malloc(65536);
 | 
			
		||||
    TEST_ASSERT_NULL_MESSAGE(x, "Allocating 64kB should fail and return null");
 | 
			
		||||
 | 
			
		||||
    void *y = malloc(32768);
 | 
			
		||||
    TEST_ASSERT_NOT_NULL_MESSAGE(y, "Allocating 32kB should succeed");
 | 
			
		||||
 | 
			
		||||
    void *z = malloc(32768);
 | 
			
		||||
    TEST_ASSERT_NULL_MESSAGE(z, "Allocating second 32kB should fail");
 | 
			
		||||
 | 
			
		||||
    free(y);
 | 
			
		||||
    z = malloc(32768);
 | 
			
		||||
    TEST_ASSERT_NOT_NULL_MESSAGE(z, "Allocating 32kB should succeed after first block freed");
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										481
									
								
								tests/cases/03_byte_load_flash.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								tests/cases/03_byte_load_flash.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,481 @@
 | 
			
		|||
/**
 | 
			
		||||
 * 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"
 | 
			
		||||
#include "esp/uart.h"
 | 
			
		||||
#include "espressif/esp_common.h"
 | 
			
		||||
#include "xtensa_ops.h"
 | 
			
		||||
#include "FreeRTOS.h"
 | 
			
		||||
#include "task.h"
 | 
			
		||||
#include "queue.h"
 | 
			
		||||
 | 
			
		||||
#include "string.h"
 | 
			
		||||
#include "strings.h"
 | 
			
		||||
 | 
			
		||||
#include <malloc.h>
 | 
			
		||||
 | 
			
		||||
#define TESTSTRING "O hai there! %d %d %d"
 | 
			
		||||
 | 
			
		||||
static char dramtest[] = TESTSTRING;
 | 
			
		||||
 | 
			
		||||
static const __attribute__((section(".iram1.notrodata")))
 | 
			
		||||
    char iramtest[] = TESTSTRING;
 | 
			
		||||
 | 
			
		||||
static const __attribute__((section(".text.notrodata")))
 | 
			
		||||
    char iromtest[] = TESTSTRING;
 | 
			
		||||
 | 
			
		||||
static const volatile __attribute__((section(".iram1.notliterals")))
 | 
			
		||||
    int16_t unsigned_shorts[] = { -3, -4, -5, -32767, 44 };
 | 
			
		||||
 | 
			
		||||
static const __attribute__((section(".iram1.notrodata")))
 | 
			
		||||
    char sanity_test_data[] = {
 | 
			
		||||
        0x01, 0x55, 0x7e, 0x2a, 0x81, 0xd5, 0xfe, 0xaa
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_verify_sections)
 | 
			
		||||
 | 
			
		||||
#define PTR_IN_REGION(PTR, START, LEN) \
 | 
			
		||||
        ((START <= (intptr_t)(PTR)) && ((intptr_t)(PTR) < (START+LEN)))
 | 
			
		||||
 | 
			
		||||
/* Sanity check, ensure the addresses of the various test strings
 | 
			
		||||
 * are in the correct address space regions. */
 | 
			
		||||
static void a_03_byte_load_verify_sections()
 | 
			
		||||
{
 | 
			
		||||
  printf("dramtest addr %p\n", dramtest);
 | 
			
		||||
  TEST_ASSERT_MESSAGE(PTR_IN_REGION(dramtest, 0x3FFE8000, 0x14000),
 | 
			
		||||
          "dramtest should be in DRAM region");
 | 
			
		||||
 | 
			
		||||
  printf("iramtest addr %p\n", iramtest);
 | 
			
		||||
  TEST_ASSERT_MESSAGE(PTR_IN_REGION(iramtest, 0x40100000, 0x8000),
 | 
			
		||||
          "iramtest should be in IRAM region");
 | 
			
		||||
 | 
			
		||||
  printf("iromtest addr %p\n", iromtest);
 | 
			
		||||
  TEST_ASSERT_MESSAGE(PTR_IN_REGION(iromtest, 0x40202010, (0x100000 - 0x2010)),
 | 
			
		||||
          "iromtest sohuld be in IROM region");
 | 
			
		||||
 | 
			
		||||
  printf("unsigned_shorts addr %p\n", unsigned_shorts);
 | 
			
		||||
  TEST_ASSERT_MESSAGE(PTR_IN_REGION(unsigned_shorts, 0x40100000, 0x8000),
 | 
			
		||||
          "unsigned_shorts should be in IRAM region");
 | 
			
		||||
 | 
			
		||||
  printf("sanity_test_data addr %p\n", sanity_test_data);
 | 
			
		||||
  TEST_ASSERT_MESSAGE(PTR_IN_REGION(sanity_test_data, 0x40100000, 0x8000),
 | 
			
		||||
          "sanity_test_data should be in IRAM region");
 | 
			
		||||
 | 
			
		||||
  TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* test utility functions used for '03_byte_load_test_strings'
 | 
			
		||||
 | 
			
		||||
   returns the expected string result */
 | 
			
		||||
typedef const char *(* test_with_fn_t)(const char *string);
 | 
			
		||||
 | 
			
		||||
static char buf[64];
 | 
			
		||||
 | 
			
		||||
static const char * test_memcpy_aligned(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    memcpy(buf, string, 16);
 | 
			
		||||
    return "O hai there! %d ";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_memcpy_unaligned(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    memcpy(buf, string, 15);
 | 
			
		||||
    return "O hai there! %d";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static const char * test_memcpy_unaligned2(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    memcpy(buf, string+1, 15);
 | 
			
		||||
    return " hai there! %d ";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_strcpy(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    strcpy(buf, string);
 | 
			
		||||
    return dramtest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_sprintf(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    sprintf(buf, string, 1, 2, 3);
 | 
			
		||||
    return "O hai there! 1 2 3";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_sprintf_arg(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    sprintf(buf, "%s", string);
 | 
			
		||||
    return dramtest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    char *to = buf;
 | 
			
		||||
    while((*to++ = *string++))
 | 
			
		||||
        ;
 | 
			
		||||
    return dramtest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a0(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a0, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a0, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a0, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a0", "a8", "a9");
 | 
			
		||||
    return dramtest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a2(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a2, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a2, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a2, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a2", "a8", "a9");
 | 
			
		||||
    return dramtest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a3(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a3, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a3, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a3, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a3", "a8", "a9");
 | 
			
		||||
    return TESTSTRING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a4(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a4, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a4, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a4, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a4", "a8", "a9");
 | 
			
		||||
    return TESTSTRING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a5(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a5, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a5, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a5, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a5", "a8", "a9");
 | 
			
		||||
    return TESTSTRING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_naive_strcpy_a6(const char *string)
 | 
			
		||||
{
 | 
			
		||||
    asm volatile (
 | 
			
		||||
"            mov          a8, %0    \n"
 | 
			
		||||
"            mov          a9, %1    \n"
 | 
			
		||||
"tns_loop%=: l8ui         a6, a9, 0 \n"
 | 
			
		||||
"            addi.n       a9, a9, 1 \n"
 | 
			
		||||
"            s8i          a6, a8, 0 \n"
 | 
			
		||||
"            addi.n       a8, a8, 1 \n"
 | 
			
		||||
"            bnez         a6, tns_loop%=\n"
 | 
			
		||||
        : : "r" (buf), "r" (string) : "a6", "a8", "a9");
 | 
			
		||||
    return TESTSTRING;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char * test_noop(const char *string)
 | 
			
		||||
{
 | 
			
		||||
  buf[0] = 0;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t IRAM inner_string_test(const char *string, test_with_fn_t testfn, const char *testfn_label, uint32_t nullvalue, bool evict_cache)
 | 
			
		||||
{
 | 
			
		||||
    printf(" .. against %30s: ", testfn_label);
 | 
			
		||||
    vPortEnterCritical();
 | 
			
		||||
    uint32_t before;
 | 
			
		||||
    RSR(before, CCOUNT);
 | 
			
		||||
    const int TEST_REPEATS = 1000;
 | 
			
		||||
    for(int i = 0; i < TEST_REPEATS; i++) {
 | 
			
		||||
      memset(buf, 0, sizeof(buf));
 | 
			
		||||
      const char *expected = testfn(string);
 | 
			
		||||
      TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buf, testfn_label);
 | 
			
		||||
      if(evict_cache) {
 | 
			
		||||
        Cache_Read_Disable();
 | 
			
		||||
        Cache_Read_Enable(0,0,1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    uint32_t after;
 | 
			
		||||
    RSR(after, CCOUNT);
 | 
			
		||||
    vPortExitCritical();
 | 
			
		||||
    uint32_t instructions = (after-before)/TEST_REPEATS - nullvalue;
 | 
			
		||||
    printf("%5d instructions\r\n", instructions);
 | 
			
		||||
    return instructions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void string_test(const char *string, char *label, bool evict_cache)
 | 
			
		||||
{
 | 
			
		||||
    printf("Testing %s (%p) '%s'\r\n", label, string, string);
 | 
			
		||||
    printf("Formats as: '");
 | 
			
		||||
    printf(string, 1, 2, 3);
 | 
			
		||||
    printf("'\r\n");
 | 
			
		||||
    uint32_t nullvalue = inner_string_test(string, test_noop, "null op", 0, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_memcpy_aligned, "memcpy - aligned len", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_memcpy_unaligned, "memcpy - unaligned len", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_memcpy_unaligned2, "memcpy - unaligned start&len", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_strcpy, "strcpy", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy, "naive strcpy", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a0, "naive strcpy (a0)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a2, "naive strcpy (a2)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a3, "naive strcpy (a3)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a4, "naive strcpy (a4)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a5, "naive strcpy (a5)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_naive_strcpy_a6, "naive strcpy (a6)", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_sprintf, "sprintf", nullvalue, evict_cache);
 | 
			
		||||
    inner_string_test(string, test_sprintf_arg, "sprintf format arg", nullvalue, evict_cache);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_test_strings)
 | 
			
		||||
 | 
			
		||||
/* Test various operations on strings in various regions */
 | 
			
		||||
static void a_03_byte_load_test_strings()
 | 
			
		||||
{
 | 
			
		||||
    string_test(dramtest, "DRAM", 0);
 | 
			
		||||
    string_test(iramtest, "IRAM", 0);
 | 
			
		||||
    string_test(iromtest, "Cached flash", 0);
 | 
			
		||||
    string_test(iromtest, "'Uncached' flash", 1);
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static volatile bool frc1_ran;
 | 
			
		||||
static volatile bool frc1_finished;
 | 
			
		||||
static volatile char frc1_buf[80];
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_test_isr)
 | 
			
		||||
 | 
			
		||||
static void frc1_interrupt_handler(void)
 | 
			
		||||
{
 | 
			
		||||
    frc1_ran = true;
 | 
			
		||||
    timer_set_run(FRC1, false);
 | 
			
		||||
    strcpy((char *)frc1_buf, iramtest);
 | 
			
		||||
    frc1_finished = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Verify that the unaligned loader can run inside an ISR */
 | 
			
		||||
static void a_03_byte_load_test_isr()
 | 
			
		||||
{
 | 
			
		||||
    printf("Testing behaviour inside ISRs...\r\n");
 | 
			
		||||
    timer_set_interrupts(FRC1, false);
 | 
			
		||||
    timer_set_run(FRC1, false);
 | 
			
		||||
    _xt_isr_attach(INUM_TIMER_FRC1, frc1_interrupt_handler);
 | 
			
		||||
    timer_set_frequency(FRC1, 1000);
 | 
			
		||||
    timer_set_interrupts(FRC1, true);
 | 
			
		||||
    timer_set_run(FRC1, true);
 | 
			
		||||
    sdk_os_delay_us(2000);
 | 
			
		||||
 | 
			
		||||
    if(!frc1_ran)
 | 
			
		||||
      TEST_FAIL_MESSAGE("ERROR: FRC1 timer exception never fired.\r\n");
 | 
			
		||||
    else if(!frc1_finished)
 | 
			
		||||
      TEST_FAIL_MESSAGE("ERROR: FRC1 timer exception never finished.\r\n");
 | 
			
		||||
    else if(strcmp((char *)frc1_buf, iramtest))
 | 
			
		||||
      TEST_FAIL_MESSAGE("ERROR: FRC1 strcpy from IRAM failed.\r\n");
 | 
			
		||||
    else
 | 
			
		||||
      TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_test_sign_extension)
 | 
			
		||||
 | 
			
		||||
static void a_03_byte_load_test_sign_extension()
 | 
			
		||||
{
 | 
			
		||||
    /* this step seems to be necessary so the compiler will actually generate l16si */
 | 
			
		||||
    int16_t *shorts_p = (int16_t *)unsigned_shorts;
 | 
			
		||||
    if(shorts_p[0] == -3 && shorts_p[1] == -4 && shorts_p[2] == -5 && shorts_p[3] == -32767 && shorts_p[4] == 44)
 | 
			
		||||
    {
 | 
			
		||||
      TEST_PASS();
 | 
			
		||||
    } else {
 | 
			
		||||
      sprintf(buf, "l16si sign extension failed. Got values %d %d %d %d %d\r\n", shorts_p[0], shorts_p[1], shorts_p[2], shorts_p[3], shorts_p[4]);
 | 
			
		||||
      TEST_FAIL_MESSAGE(buf);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* test that running unaligned loads in a running FreeRTOS system doesn't break things
 | 
			
		||||
 | 
			
		||||
   The following tests run inside a FreeRTOS task, after everything else.
 | 
			
		||||
*/
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_test_system_interaction);
 | 
			
		||||
 | 
			
		||||
static void task_load_test_system_interaction()
 | 
			
		||||
{
 | 
			
		||||
    uint32_t start = xTaskGetTickCount();
 | 
			
		||||
    printf("Starting system/timer interaction test (takes approx 1 second)...\n");
 | 
			
		||||
    for(int i = 0; i < 5000; i++) {
 | 
			
		||||
        test_naive_strcpy_a0(iromtest);
 | 
			
		||||
        test_naive_strcpy_a2(iromtest);
 | 
			
		||||
        test_naive_strcpy_a3(iromtest);
 | 
			
		||||
        test_naive_strcpy_a4(iromtest);
 | 
			
		||||
        test_naive_strcpy_a5(iromtest);
 | 
			
		||||
        test_naive_strcpy_a6(iromtest);
 | 
			
		||||
        /*
 | 
			
		||||
        const volatile char *string = iromtest;
 | 
			
		||||
        volatile char *to = dest;
 | 
			
		||||
        while((*to++ = *string++))
 | 
			
		||||
            ;
 | 
			
		||||
        */
 | 
			
		||||
    }
 | 
			
		||||
    uint32_t ticks = xTaskGetTickCount() - start;
 | 
			
		||||
    printf("Timer interaction test PASSED after %d ticks.\n", ticks);
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void a_03_byte_load_test_system_interaction()
 | 
			
		||||
{
 | 
			
		||||
    xTaskCreate(task_load_test_system_interaction, "interactionTask", 256, NULL, 2, NULL);
 | 
			
		||||
    while(1) {
 | 
			
		||||
      vTaskDelay(100);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* The following "sanity tests" are designed to try to execute every code path
 | 
			
		||||
 * of the LoadStoreError handler, with a variety of offsets and data values
 | 
			
		||||
 * designed to catch any mask/shift errors, sign-extension bugs, etc */
 | 
			
		||||
DEFINE_SOLO_TESTCASE(03_byte_load_test_sanity)
 | 
			
		||||
 | 
			
		||||
/* (Contrary to expectations, 'mov a15, a15' in Xtensa is not technically a
 | 
			
		||||
 * no-op, but is officially "undefined and reserved for future use", so we need
 | 
			
		||||
 * a special case in the case where reg == "a15" so we don't end up generating
 | 
			
		||||
 * those opcodes.  GCC is smart enough to optimize away the whole conditional
 | 
			
		||||
 * and just insert the correct asm block, since `reg` is a static argument.) */
 | 
			
		||||
#define LOAD_VIA_REG(op, reg, addr, var) \
 | 
			
		||||
    if (strcmp(reg, "a15")) { \
 | 
			
		||||
        asm volatile ( \
 | 
			
		||||
        "mov a15, " reg "\n\t" \
 | 
			
		||||
        op " " reg ", %1, 0\n\t" \
 | 
			
		||||
        "mov %0, " reg "\n\t" \
 | 
			
		||||
        "mov " reg ", a15\n\t" \
 | 
			
		||||
        : "=r" (var) : "r" (addr) : "a15" ); \
 | 
			
		||||
    } else { \
 | 
			
		||||
        asm volatile ( \
 | 
			
		||||
        op " " reg ", %1, 0\n\t" \
 | 
			
		||||
        "mov %0, " reg "\n\t" \
 | 
			
		||||
        : "=r" (var) : "r" (addr) : "a15" ); \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#define TEST_LOAD(op, reg, addr, value) \
 | 
			
		||||
    { \
 | 
			
		||||
        int32_t result; \
 | 
			
		||||
        LOAD_VIA_REG(op, reg, addr, result); \
 | 
			
		||||
        if (result != value) sanity_test_failed(op, reg, addr, value, result); \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
static void sanity_test_failed(const char *testname, const char *reg, const void *addr, int32_t value, int32_t result) {
 | 
			
		||||
    uint32_t actual_data = *(uint32_t *)((uint32_t)addr & 0xfffffffc);
 | 
			
		||||
    sprintf(buf, "%s %s from %p (32-bit value: 0x%x): Expected 0x%08x (%d), got 0x%08x (%d)\n", testname, reg, addr, actual_data, value, value, result, result);
 | 
			
		||||
    TEST_FAIL_MESSAGE(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sanity_test_l8ui(const void *addr, int32_t value) {
 | 
			
		||||
    TEST_LOAD("l8ui", "a0", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a1", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a2", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a3", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a4", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a5", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a6", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a7", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a8", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a9", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a10", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a11", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a12", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a13", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a14", addr, value);
 | 
			
		||||
    TEST_LOAD("l8ui", "a15", addr, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sanity_test_l16ui(const void *addr, int32_t value) {
 | 
			
		||||
    TEST_LOAD("l16ui", "a0", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a1", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a2", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a3", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a4", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a5", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a6", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a7", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a8", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a9", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a10", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a11", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a12", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a13", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a14", addr, value);
 | 
			
		||||
    TEST_LOAD("l16ui", "a15", addr, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sanity_test_l16si(const void *addr, int32_t value) {
 | 
			
		||||
    TEST_LOAD("l16si", "a0", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a1", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a2", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a3", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a4", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a5", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a6", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a7", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a8", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a9", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a10", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a11", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a12", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a13", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a14", addr, value);
 | 
			
		||||
    TEST_LOAD("l16si", "a15", addr, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void a_03_byte_load_test_sanity(void) {
 | 
			
		||||
    printf("== Performing sanity tests (sanity_test_data @ %p)...\n", sanity_test_data);
 | 
			
		||||
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 0, 0x01);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 1, 0x55);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 2, 0x7e);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 3, 0x2a);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 4, 0x81);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 5, 0xd5);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 6, 0xfe);
 | 
			
		||||
    sanity_test_l8ui(sanity_test_data + 7, 0xaa);
 | 
			
		||||
 | 
			
		||||
    sanity_test_l16ui(sanity_test_data + 0, 0x5501);
 | 
			
		||||
    sanity_test_l16ui(sanity_test_data + 2, 0x2a7e);
 | 
			
		||||
    sanity_test_l16ui(sanity_test_data + 4, 0xd581);
 | 
			
		||||
    sanity_test_l16ui(sanity_test_data + 6, 0xaafe);
 | 
			
		||||
 | 
			
		||||
    sanity_test_l16si(sanity_test_data + 0, 0x5501);
 | 
			
		||||
    sanity_test_l16si(sanity_test_data + 2, 0x2a7e);
 | 
			
		||||
    sanity_test_l16si(sanity_test_data + 4, -10879);
 | 
			
		||||
    sanity_test_l16si(sanity_test_data + 6, -21762);
 | 
			
		||||
 | 
			
		||||
    printf("== Sanity tests completed.\n");
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								tests/cases/04_wifi_basic.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								tests/cases/04_wifi_basic.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 <string.h>
 | 
			
		||||
 | 
			
		||||
#include <FreeRTOS.h>
 | 
			
		||||
#include <task.h>
 | 
			
		||||
 | 
			
		||||
#include <espressif/esp_common.h>
 | 
			
		||||
#include "sdk_internal.h"
 | 
			
		||||
 | 
			
		||||
#include <lwip/api.h>
 | 
			
		||||
#include <lwip/err.h>
 | 
			
		||||
#include <lwip/sockets.h>
 | 
			
		||||
#include <lwip/sys.h>
 | 
			
		||||
#include <lwip/netdb.h>
 | 
			
		||||
#include <lwip/dns.h>
 | 
			
		||||
#include <dhcpserver.h>
 | 
			
		||||
 | 
			
		||||
#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_PERIOD_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, "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_PERIOD_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_PERIOD_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, "connect_task", 1024, NULL, 2, NULL);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,14 +9,18 @@
 | 
			
		|||
#include "esp_spiffs.h"
 | 
			
		||||
#include "spiffs.h"
 | 
			
		||||
 | 
			
		||||
#include "fs-test/fs_test.h"
 | 
			
		||||
#include "fs_test.h"
 | 
			
		||||
 | 
			
		||||
#include "testcase.h"
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(05_spiffs)
 | 
			
		||||
 | 
			
		||||
static fs_time_t get_current_time()
 | 
			
		||||
{
 | 
			
		||||
     return timer_get_count(FRC2) / 5000;  // to get roughly 1ms resolution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_task(void *pvParameters)
 | 
			
		||||
static void test_task(void *pvParameters)
 | 
			
		||||
{
 | 
			
		||||
    esp_spiffs_init();
 | 
			
		||||
    esp_spiffs_mount();
 | 
			
		||||
| 
						 | 
				
			
			@ -28,28 +32,19 @@ void test_task(void *pvParameters)
 | 
			
		|||
    }
 | 
			
		||||
    esp_spiffs_mount();
 | 
			
		||||
 | 
			
		||||
    while (1) {
 | 
			
		||||
        vTaskDelay(5000 / portTICK_PERIOD_MS);
 | 
			
		||||
        if (fs_load_test_run(100)) {
 | 
			
		||||
            printf("PASS\n");
 | 
			
		||||
        } else {
 | 
			
		||||
            printf("FAIL\n");
 | 
			
		||||
        }
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(fs_load_test_run(100), "Load test failed");
 | 
			
		||||
 | 
			
		||||
        vTaskDelay(5000 / portTICK_PERIOD_MS);
 | 
			
		||||
        float write_rate, read_rate;
 | 
			
		||||
        if (fs_speed_test_run(get_current_time, &write_rate, &read_rate)) {
 | 
			
		||||
            printf("Read speed: %.0f bytes/s\n", read_rate * 1000); 
 | 
			
		||||
            printf("Write speed: %.0f bytes/s\n", write_rate * 1000); 
 | 
			
		||||
        } else {
 | 
			
		||||
            printf("FAIL\n");
 | 
			
		||||
        }
 | 
			
		||||
    float write_rate, read_rate;
 | 
			
		||||
    if (fs_speed_test_run(get_current_time, &write_rate, &read_rate)) {
 | 
			
		||||
        printf("Read speed: %.0f bytes/s\n", read_rate * 1000);
 | 
			
		||||
        printf("Write speed: %.0f bytes/s\n", write_rate * 1000);
 | 
			
		||||
    } else {
 | 
			
		||||
        TEST_FAIL();
 | 
			
		||||
    }
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void user_init(void)
 | 
			
		||||
static void a_05_spiffs(void)
 | 
			
		||||
{
 | 
			
		||||
    uart_set_baud(0, 115200);
 | 
			
		||||
 | 
			
		||||
    xTaskCreate(test_task, "test_task", 1024, NULL, 2, NULL);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								tests/cases/06_timers.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/cases/06_timers.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include "FreeRTOS.h"
 | 
			
		||||
#include "task.h"
 | 
			
		||||
#include "etstimer.h"
 | 
			
		||||
 | 
			
		||||
#include "testcase.h"
 | 
			
		||||
 | 
			
		||||
DEFINE_SOLO_TESTCASE(06_ets_timers);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    ETSTimer handle;
 | 
			
		||||
    uint32_t start_time;
 | 
			
		||||
    uint32_t fire_count;
 | 
			
		||||
} test_timer_t;
 | 
			
		||||
 | 
			
		||||
#define TEST_TIMERS_NUMBER 2
 | 
			
		||||
static test_timer_t timers[TEST_TIMERS_NUMBER];
 | 
			
		||||
 | 
			
		||||
static uint32_t get_current_time()
 | 
			
		||||
{
 | 
			
		||||
     return timer_get_count(FRC2) / 5000;  // to get roughly 1ms resolution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void timer_0_cb(void *arg)
 | 
			
		||||
{
 | 
			
		||||
    uint32_t v = (uint32_t)arg;
 | 
			
		||||
    uint32_t delay = get_current_time() - timers[0].start_time;
 | 
			
		||||
    timers[0].fire_count++;
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0xAA, v, "Timer 0 argument invalid");
 | 
			
		||||
    TEST_ASSERT_EQUAL_INT_MESSAGE(1, timers[0].fire_count, "Timer 0 repeat error");
 | 
			
		||||
 | 
			
		||||
    printf("Timer 0 delay: %d\n", delay);
 | 
			
		||||
    // Timer should fire in 100ms
 | 
			
		||||
    TEST_ASSERT_INT_WITHIN_MESSAGE(5, 100, delay, "Timer 0 time wrong");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void timer_1_cb(void *arg)
 | 
			
		||||
{
 | 
			
		||||
    uint32_t v = (uint32_t)arg;
 | 
			
		||||
    uint32_t delay = get_current_time() - timers[1].start_time;
 | 
			
		||||
 | 
			
		||||
    timers[1].start_time = get_current_time();
 | 
			
		||||
    timers[1].fire_count++;
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0xBB, v, "Timer 1 argument invalid");
 | 
			
		||||
    TEST_ASSERT_TRUE_MESSAGE(timers[1].fire_count < 6,
 | 
			
		||||
            "Timer 1 repeats after disarming");
 | 
			
		||||
 | 
			
		||||
    printf("Timer 1 delay: %d\n", delay);
 | 
			
		||||
    // Timer should fire in 100ms
 | 
			
		||||
    TEST_ASSERT_INT_WITHIN_MESSAGE(5, 50, delay, "Timer 1 time wrong");
 | 
			
		||||
 | 
			
		||||
    if (timers[1].fire_count == 5) {
 | 
			
		||||
        sdk_ets_timer_disarm(&timers[1].handle);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void test_task(void *pvParameters)
 | 
			
		||||
{
 | 
			
		||||
    sdk_ets_timer_disarm(&timers[0].handle);
 | 
			
		||||
    sdk_ets_timer_setfn(&timers[0].handle, timer_0_cb, (void*)0xAA);
 | 
			
		||||
    timers[0].start_time = get_current_time();
 | 
			
		||||
    sdk_ets_timer_arm(&timers[0].handle, 100, false);
 | 
			
		||||
 | 
			
		||||
    sdk_ets_timer_disarm(&timers[1].handle);
 | 
			
		||||
    sdk_ets_timer_setfn(&timers[1].handle, timer_1_cb, (void*)0xBB);
 | 
			
		||||
    timers[1].start_time = get_current_time();
 | 
			
		||||
    sdk_ets_timer_arm(&timers[1].handle, 50, true);   // repeating timer
 | 
			
		||||
 | 
			
		||||
    vTaskDelay(500 / portTICK_PERIOD_MS);
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_EQUAL_INT_MESSAGE(1, timers[0].fire_count,
 | 
			
		||||
            "Timer hasn't fired");
 | 
			
		||||
 | 
			
		||||
    TEST_ASSERT_EQUAL_INT_MESSAGE(5, timers[1].fire_count,
 | 
			
		||||
            "Timer fire count isn't correct");
 | 
			
		||||
 | 
			
		||||
    TEST_PASS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void a_06_ets_timers(void)
 | 
			
		||||
{
 | 
			
		||||
    xTaskCreate(test_task, "test_task", 256, NULL, 2, NULL);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								tests/fs-test
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								tests/fs-test
									
										
									
									
									
										Submodule
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Subproject commit e531bc0d4f75887e5f0e081aae3efbf4a50e2f54
 | 
			
		||||
							
								
								
									
										59
									
								
								tests/include/testcase.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								tests/include/testcase.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
#ifndef _TESTCASE_H
 | 
			
		||||
#define _TESTCASE_H
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include "esp/uart.h"
 | 
			
		||||
 | 
			
		||||
/* Unity is the framework with test assertions, etc. */
 | 
			
		||||
#include "unity.h"
 | 
			
		||||
 | 
			
		||||
/* Need to explicitly flag once a test has completed successfully. */
 | 
			
		||||
#undef TEST_PASS
 | 
			
		||||
#define TEST_PASS() do { UnityConcludeTest(); while(1) { } } while (0)
 | 
			
		||||
 | 
			
		||||
/* Types of test, defined by hardware requirements */
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SOLO,        /* Test require "ESP A" only, no other connections */
 | 
			
		||||
    DUAL,        /* Test requires "ESP A" and "ESP "B", basic interconnections between them */
 | 
			
		||||
    EYORE_TEST,  /* Test requires an eyore-test board with onboard STM32F0 */
 | 
			
		||||
} testcase_type_t;
 | 
			
		||||
 | 
			
		||||
typedef void (testcase_fn_t)(void);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    const char *name;
 | 
			
		||||
    const char *file;
 | 
			
		||||
    int line;
 | 
			
		||||
    testcase_type_t type;
 | 
			
		||||
    testcase_fn_t *a_fn;
 | 
			
		||||
    testcase_fn_t *b_fn;
 | 
			
		||||
} testcase_t;
 | 
			
		||||
 | 
			
		||||
void testcase_register(const testcase_t *testcase);
 | 
			
		||||
 | 
			
		||||
/* Register a test case using these macros. Use DEFINE_SOLO_TESTCASE for single-MCU tests,
 | 
			
		||||
   and DEFINE_TESTCASE for all other test types.
 | 
			
		||||
*/
 | 
			
		||||
#define DEFINE_SOLO_TESTCASE(NAME)                                      \
 | 
			
		||||
    static testcase_fn_t a_##NAME;                                      \
 | 
			
		||||
    _DEFINE_TESTCASE_COMMON(NAME, SOLO, a_##NAME, 0)
 | 
			
		||||
 | 
			
		||||
#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 _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_FN, B_FN)                 \
 | 
			
		||||
    void __attribute__((constructor)) testcase_ctor_##NAME() {          \
 | 
			
		||||
        const testcase_t testcase = { .name = #NAME,                    \
 | 
			
		||||
                                      .file = __FILE__,                 \
 | 
			
		||||
                                      .line = __LINE__,                 \
 | 
			
		||||
                                      .type = TYPE,                     \
 | 
			
		||||
                                      .a_fn = A_FN,                     \
 | 
			
		||||
                                      .b_fn = B_FN,                     \
 | 
			
		||||
        };                                                              \
 | 
			
		||||
        testcase_register(&testcase);                                   \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										122
									
								
								tests/test_main.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								tests/test_main.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,122 @@
 | 
			
		|||
#include "testcase.h"
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <esp/uart.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <FreeRTOS.h>
 | 
			
		||||
#include <task.h>
 | 
			
		||||
#include <espressif/esp_common.h>
 | 
			
		||||
 | 
			
		||||
/* Convert requirement enum to a string we can print */
 | 
			
		||||
static const char *get_requirements_name(const testcase_type_t arg) {
 | 
			
		||||
    switch(arg) {
 | 
			
		||||
    case SOLO:
 | 
			
		||||
        return "SOLO";
 | 
			
		||||
    case DUAL:
 | 
			
		||||
        return "DUAL";
 | 
			
		||||
    case EYORE_TEST:
 | 
			
		||||
        return "EYORE_TEST";
 | 
			
		||||
    default:
 | 
			
		||||
        return "UNKNOWN";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static testcase_t *testcases;
 | 
			
		||||
static uint32_t testcases_count;
 | 
			
		||||
static uint32_t testcases_alloc;
 | 
			
		||||
 | 
			
		||||
void testcase_register(const testcase_t *testcase)
 | 
			
		||||
{
 | 
			
		||||
    /* Grow the testcases buffer to fit the new test case,
 | 
			
		||||
       this buffer will be freed before the test runs.
 | 
			
		||||
     */
 | 
			
		||||
    if(testcases_count == testcases_alloc) {
 | 
			
		||||
        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);
 | 
			
		||||
            testcases_count = 0;
 | 
			
		||||
            testcases_alloc = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    memcpy(&testcases[testcases_count++], testcase, sizeof(testcase_t));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void user_init(void)
 | 
			
		||||
{
 | 
			
		||||
    uart_set_baud(0, 115200);
 | 
			
		||||
    sdk_wifi_set_opmode(NULL_MODE);
 | 
			
		||||
    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("Enter A or B then number of test case to run, ie A0.\n");
 | 
			
		||||
    int case_idx = -1;
 | 
			
		||||
    char type;
 | 
			
		||||
    do {
 | 
			
		||||
        printf("> ");
 | 
			
		||||
        uart_rxfifo_wait(0,1);
 | 
			
		||||
        type = uart_getc(0);
 | 
			
		||||
        if(type != 'a' && type != 'A' && type != 'b' && type != 'B') {
 | 
			
		||||
            printf("Type must be A or B.\n");
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        char idx_buf[6];
 | 
			
		||||
        for(int c = 0; c < sizeof(idx_buf); c++) {
 | 
			
		||||
            uart_rxfifo_wait(0,1);
 | 
			
		||||
            idx_buf[c] = uart_getc(0);
 | 
			
		||||
            if(idx_buf[c] == ' ') { /* Ignore spaces */
 | 
			
		||||
                c--;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if(idx_buf[c] == '\r' || idx_buf[c] == '\n') {
 | 
			
		||||
                idx_buf[c] = 0;
 | 
			
		||||
                case_idx = atoi(idx_buf);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else if(idx_buf[c] < '0' || idx_buf[c] > '9') {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(case_idx == -1) {
 | 
			
		||||
            printf("Invalid case index");
 | 
			
		||||
        }
 | 
			
		||||
        else if(case_idx < 0 || case_idx >= testcases_count) {
 | 
			
		||||
            printf("Test case index out of range.\n");
 | 
			
		||||
        }
 | 
			
		||||
        else if((type == 'b' || type =='B') && testcases[case_idx].type == SOLO) {
 | 
			
		||||
            printf("No ESP type B for 'SOLO' test cases.\n");
 | 
			
		||||
        } else {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    } while(1);
 | 
			
		||||
    if(type =='a')
 | 
			
		||||
        type = 'A';
 | 
			
		||||
    else if (type == 'b')
 | 
			
		||||
        type = 'B';
 | 
			
		||||
    testcase_t testcase;
 | 
			
		||||
    memcpy(&testcase, &testcases[case_idx], sizeof(testcase_t));
 | 
			
		||||
    /* Free the register of test cases now we have the one we're running */
 | 
			
		||||
    free(testcases);
 | 
			
		||||
    testcases_alloc = 0;
 | 
			
		||||
    testcases_count = 0;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    Unity.NumberOfTests = 1;
 | 
			
		||||
    if(type=='A')
 | 
			
		||||
        testcase.a_fn();
 | 
			
		||||
    else
 | 
			
		||||
        testcase.b_fn();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										389
									
								
								tests/test_runner.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										389
									
								
								tests/test_runner.py
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,389 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
import sys
 | 
			
		||||
import argparse
 | 
			
		||||
import subprocess
 | 
			
		||||
import os
 | 
			
		||||
import serial
 | 
			
		||||
import threading
 | 
			
		||||
import re
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SHORT_OUTPUT_TIMEOUT = 0.25  # timeout for resetting and/or waiting for more lines of output
 | 
			
		||||
TESTCASE_TIMEOUT = 60
 | 
			
		||||
TESTRUNNER_BANNER = "esp-open-rtos test runner."
 | 
			
		||||
RESET_RETRIES = 10  # retries to receive test runner banner after reset
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run(env_a, env_b, cases):
 | 
			
		||||
    counts = dict((status, 0) for status in TestResult.STATUS_NAMES.keys())
 | 
			
		||||
    failures = False
 | 
			
		||||
    for test in cases:
 | 
			
		||||
        if test.case_type == 'dual':
 | 
			
		||||
            if env_b is None:
 | 
			
		||||
                res = TestResult(TestResult.SKIPPED, 'Dual test case skipped')
 | 
			
		||||
            else:
 | 
			
		||||
                res = test.run(env_a, env_b)
 | 
			
		||||
        else:
 | 
			
		||||
            res = test.run(env_a)
 | 
			
		||||
        counts[res.status] += 1
 | 
			
		||||
        failures = failures or res.is_failure()
 | 
			
		||||
 | 
			
		||||
    print("%20s: %d" % ("Total tests", sum(c for c in counts.values())))
 | 
			
		||||
    print()
 | 
			
		||||
    # print status counts for tests
 | 
			
		||||
    for c in sorted(counts.keys()):
 | 
			
		||||
        print("%20s: %d" % (TestResult.STATUS_NAMES[c], counts[c]))
 | 
			
		||||
 | 
			
		||||
    return failures == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    global verbose
 | 
			
		||||
    args = parse_args()
 | 
			
		||||
    verbose = args.verbose
 | 
			
		||||
 | 
			
		||||
    if not args.no_flash:
 | 
			
		||||
        flash_image(args.aport)
 | 
			
		||||
        if args.type != 'solo':
 | 
			
		||||
            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)
 | 
			
		||||
        cases_b = env_b.get_testlist()
 | 
			
		||||
        if cases != cases_b:
 | 
			
		||||
            raise TestRunnerError("Test cases on units A & B don't match")
 | 
			
		||||
 | 
			
		||||
    if args.list:  # if list option is specified, do not run test cases
 | 
			
		||||
        print("List of test cases:")
 | 
			
		||||
        for test in cases:
 | 
			
		||||
            print(test)
 | 
			
		||||
        sys.exit(0)
 | 
			
		||||
 | 
			
		||||
    if args.testcases:  # if testcases is specified run only those cases
 | 
			
		||||
        cases = [c for c in cases if str(c.index) in args.testcases]
 | 
			
		||||
 | 
			
		||||
    sys.exit(0 if run(env, env_b, cases) else 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCase(object):
 | 
			
		||||
    def __init__(self, index, name, case_type):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.index = index
 | 
			
		||||
        self.case_type = case_type
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    def run(self, env_a, env_b=None):
 | 
			
		||||
        """
 | 
			
		||||
        Run the test represented by this instance, against the environment(s) passed in.
 | 
			
		||||
 | 
			
		||||
        Returns a TestResult
 | 
			
		||||
        """
 | 
			
		||||
        sys.stdout.write("Running test case '%s'...%s" % (self.name, "\n" if verbose else " "*(40-len(self.name))))
 | 
			
		||||
        mon_a = env_a.start_testcase(self)
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
            # or, in the case both are running, stop as soon as either environemnt shows a failure
 | 
			
		||||
            try:
 | 
			
		||||
                if mon_a.get_result().is_failure():
 | 
			
		||||
                    mon_b.cancel()
 | 
			
		||||
                    break
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
            try:
 | 
			
		||||
                if mon_b.get_result().is_failure():
 | 
			
		||||
                    mon_a.cancel()
 | 
			
		||||
                    break
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
            time.sleep(0.1)
 | 
			
		||||
 | 
			
		||||
        if mon_b is not None:
 | 
			
		||||
            # return whichever result is more severe
 | 
			
		||||
            res = max(mon_a.get_result(), mon_b.get_result())
 | 
			
		||||
        else:
 | 
			
		||||
            res = mon_a.get_result()
 | 
			
		||||
        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
 | 
			
		||||
                    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
 | 
			
		||||
    CANCELLED = 0
 | 
			
		||||
    SKIPPED = 1
 | 
			
		||||
    PASSED = 2
 | 
			
		||||
    FAILED = 3
 | 
			
		||||
    ERROR = 4
 | 
			
		||||
 | 
			
		||||
    STATUS_NAMES = {
 | 
			
		||||
        CANCELLED: "Cancelled",
 | 
			
		||||
        SKIPPED: "Skipped",
 | 
			
		||||
        PASSED: "Passed",
 | 
			
		||||
        FAILED: "Failed",
 | 
			
		||||
        ERROR: "Error"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, status, message):
 | 
			
		||||
        self.status = status
 | 
			
		||||
        self.message = message
 | 
			
		||||
 | 
			
		||||
    def is_failure(self):
 | 
			
		||||
        return self.status >= TestResult.FAILED
 | 
			
		||||
 | 
			
		||||
    def __qe__(self, other):
 | 
			
		||||
        if other is None:
 | 
			
		||||
            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.
 | 
			
		||||
 | 
			
		||||
    Can poll for completion by calling is_done(), read a TestResult via .get_result()
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, port, instance):
 | 
			
		||||
        super(TestMonitor, self).__init__()
 | 
			
		||||
        self._thread = threading.Thread(target=self._monitorThread)
 | 
			
		||||
        self._port = port
 | 
			
		||||
        self._instance = instance
 | 
			
		||||
        self._result = None
 | 
			
		||||
        self._cancelled = False
 | 
			
		||||
        self.output = ""
 | 
			
		||||
        self._thread.start()
 | 
			
		||||
 | 
			
		||||
    def cancel(self):
 | 
			
		||||
        self._cancelled = True
 | 
			
		||||
 | 
			
		||||
    def is_done(self):
 | 
			
		||||
        return self._result is not None
 | 
			
		||||
 | 
			
		||||
    def get_result(self):
 | 
			
		||||
        return self._result
 | 
			
		||||
 | 
			
		||||
    def _monitorThread(self):
 | 
			
		||||
        self.output = ""
 | 
			
		||||
        start_time = time.time()
 | 
			
		||||
        self._port.timeout = SHORT_OUTPUT_TIMEOUT
 | 
			
		||||
        try:
 | 
			
		||||
            while not self._cancelled and time.time() < start_time + TESTCASE_TIMEOUT:
 | 
			
		||||
                line = self._port.readline().decode("utf-8", "ignore")
 | 
			
		||||
                if line == "":
 | 
			
		||||
                    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"):
 | 
			
		||||
                    self._result = TestResult(TestResult.PASSED, "Test passed.")
 | 
			
		||||
                    return
 | 
			
		||||
                elif ":FAIL:" in line:
 | 
			
		||||
                    self._result = TestResult(TestResult.FAILED, line)
 | 
			
		||||
                    return
 | 
			
		||||
                elif line == TESTRUNNER_BANNER:
 | 
			
		||||
                    self._result = TestResult(TestResult.ERROR, "Test caused crash and reset.")
 | 
			
		||||
                    return
 | 
			
		||||
            if not self._cancelled:
 | 
			
		||||
                self._result = TestResult(TestResult.CANCELLED, "Cancelled")
 | 
			
		||||
            else:
 | 
			
		||||
                self._result = TestResult(TestResult.ERROR, "Test timed out")
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            self._port.timeout = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEnvironment(object):
 | 
			
		||||
    A = "A"
 | 
			
		||||
    B = "B"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, port, instance):
 | 
			
		||||
        self._name = port
 | 
			
		||||
        self._port = TestSerialPort(port, baudrate=115200)
 | 
			
		||||
        self._instance = instance
 | 
			
		||||
 | 
			
		||||
    def reset(self):
 | 
			
		||||
        """ Resets the test board, and waits for the test runner program to start up """
 | 
			
		||||
        for i in range(RESET_RETRIES):
 | 
			
		||||
            self._port.setDTR(False)
 | 
			
		||||
            self._port.setRTS(True)
 | 
			
		||||
            time.sleep(0.05)
 | 
			
		||||
            self._port.flushInput()
 | 
			
		||||
            self._port.setRTS(False)
 | 
			
		||||
            verbose_print("Waiting for test runner startup...")
 | 
			
		||||
            if self._port.wait_line(lambda line: line == TESTRUNNER_BANNER):
 | 
			
		||||
                return
 | 
			
		||||
            else:
 | 
			
		||||
                verbose_print("Retrying to reset the test board, attempt=%d" %
 | 
			
		||||
                              (i + 1))
 | 
			
		||||
                continue
 | 
			
		||||
            raise TestRunnerError("Port %s failed to start test runner" % self._port)
 | 
			
		||||
 | 
			
		||||
    def get_testlist(self):
 | 
			
		||||
        """ Resets the test board and returns the enumerated list of all supported tests """
 | 
			
		||||
        self.reset()
 | 
			
		||||
        tests = []
 | 
			
		||||
        verbose_print("Enumerating tests...")
 | 
			
		||||
 | 
			
		||||
        def collect_testcases(line):
 | 
			
		||||
                if line.startswith(">"):
 | 
			
		||||
                    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())
 | 
			
		||||
                    verbose_print(t)
 | 
			
		||||
                    tests.append(t)
 | 
			
		||||
        if not self._port.wait_line(collect_testcases):
 | 
			
		||||
            raise TestRunnerError("Port %s failed to read test list" % self._port)
 | 
			
		||||
        verbose_print("Port %s found %d test cases" % (self._name, len(tests)))
 | 
			
		||||
        return tests
 | 
			
		||||
 | 
			
		||||
    def start_testcase(self, case):
 | 
			
		||||
        """ 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(">")):
 | 
			
		||||
            raise TestRunnerError("Failed to read test runnner prompt")
 | 
			
		||||
        command = "%s%d\r\n" % (self._instance, case.index)
 | 
			
		||||
        self._port.write(command.encode("utf-8"))
 | 
			
		||||
        return TestMonitor(self._port, self._instance)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_testdir():
 | 
			
		||||
    """
 | 
			
		||||
    Return the 'tests' directory in the source tree
 | 
			
		||||
    (assuming the test_runner.py script is in that directory.
 | 
			
		||||
    """
 | 
			
		||||
    res = os.path.dirname(__name__)
 | 
			
		||||
    return "." if res == "" else res
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def flash_image(serial_port):
 | 
			
		||||
    # 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
 | 
			
		||||
        subprocess.check_call(["make", "flash"], 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))
 | 
			
		||||
    verbose_print("Flashing successful.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_args():
 | 
			
		||||
    parser = argparse.ArgumentParser(description='esp-open-rtos testrunner', prog='test_runner')
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--type', '-t',
 | 
			
		||||
        help='Type of test hardware attached to serial ports A & (optionally) B',
 | 
			
		||||
        choices=['solo', 'dual', 'eyore_test'], default='solo')
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--aport', '-a',
 | 
			
		||||
        help='Serial port for device A',
 | 
			
		||||
        default='/dev/ttyUSB0')
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--bport', '-b',
 | 
			
		||||
        help='Serial port for device B (ignored if type is \'solo\')',
 | 
			
		||||
        default='/dev/ttyUSB1')
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--no-flash', '-n',
 | 
			
		||||
        help='Don\'t flash the test binary image before running tests',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        default=False)
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--list', '-l',
 | 
			
		||||
        help='Display list of available test cases on a device',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        default=False)
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--verbose', '-v',
 | 
			
		||||
        help='Verbose test runner debugging output',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        default=False)
 | 
			
		||||
 | 
			
		||||
    parser.add_argument('testcases', nargs='*',
 | 
			
		||||
                        help='Optional list of test case numbers to run. '
 | 
			
		||||
                             'By default, all tests are run.')
 | 
			
		||||
 | 
			
		||||
    return parser.parse_args()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
        """ 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.
 | 
			
		||||
 | 
			
		||||
            Returns first non-False result from the callback, or None if it timed out waiting for a new line.
 | 
			
		||||
 | 
			
		||||
            Note that a serial port spewing legitimate lines of output may block this function forever, if callback
 | 
			
		||||
            doesn't detect this is happening.
 | 
			
		||||
        """
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        try:
 | 
			
		||||
            res = None
 | 
			
		||||
            while not res:
 | 
			
		||||
                line = self.readline()
 | 
			
		||||
                if line == b"":
 | 
			
		||||
                    break  # timed out
 | 
			
		||||
                line = line.decode("utf-8", "ignore").rstrip()
 | 
			
		||||
                res = callback(line)
 | 
			
		||||
            return res
 | 
			
		||||
        finally:
 | 
			
		||||
            self.timeout = None
 | 
			
		||||
 | 
			
		||||
verbose = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def verbose_print(msg):
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(msg)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    try:
 | 
			
		||||
        main()
 | 
			
		||||
    except TestRunnerError as e:
 | 
			
		||||
        print(e)
 | 
			
		||||
        sys.exit(2)
 | 
			
		||||
							
								
								
									
										1
									
								
								tests/unity
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								tests/unity
									
										
									
									
									
										Submodule
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Subproject commit bbf2fe3a934f96cd00693841247a689e57a17b0d
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue