examples: Add storage example tests

This commit is contained in:
Roland Dobai 2020-03-30 16:26:21 +02:00
parent d5aeae9d48
commit 819f7a4e57
18 changed files with 454 additions and 95 deletions

View file

@ -9,91 +9,6 @@ from tiny_test_fw import Utility
import ttfw_idf
class CustomProcess(object):
def __init__(self, cmd, logfile):
self.f = open(logfile, 'wb')
self.p = pexpect.spawn(cmd, timeout=60, logfile=self.f)
def __enter__(self):
return self
def close(self):
self.p.terminate(force=True)
def __exit__(self, type, value, traceback):
self.close()
self.f.close()
class OCDProcess(CustomProcess):
def __init__(self, proj_path):
cmd = 'openocd -f board/esp32-wrover-kit-3.3v.cfg'
log_file = os.path.join(proj_path, 'openocd.log')
super(OCDProcess, self).__init__(cmd, log_file)
patterns = ['Info : Listening on port 3333 for gdb connections',
'Error: type \'esp32\' is missing virt2phys']
try:
while True:
i = self.p.expect_exact(patterns, timeout=30)
# TIMEOUT or EOF exceptions will be thrown upon other errors
if i == 0:
Utility.console_log('openocd is listening for gdb connections')
break # success
elif i == 1:
Utility.console_log('Ignoring error: "{}"'.format(patterns[i]))
# this error message is ignored because it is not a fatal error
except Exception:
Utility.console_log('openocd initialization has failed', 'R')
raise
def close(self):
try:
self.p.sendcontrol('c')
self.p.expect_exact('shutdown command invoked')
except Exception:
Utility.console_log('openocd needs to be killed', 'O')
super(OCDProcess, self).close()
class GDBProcess(CustomProcess):
def __init__(self, proj_path, elf_path):
cmd = 'xtensa-esp32-elf-gdb -x {} --directory={} {}'.format(os.path.join(proj_path, '.gdbinit.ci'),
os.path.join(proj_path, 'main'),
elf_path)
log_file = os.path.join(proj_path, 'gdb.log')
super(GDBProcess, self).__init__(cmd, log_file)
self.p.sendline('') # it is for "---Type <return> to continue, or q <return> to quit---"
i = self.p.expect_exact(['Thread 1 hit Temporary breakpoint 2, app_main ()',
'Load failed'])
if i == 0:
Utility.console_log('gdb is at breakpoint')
elif i == 1:
raise RuntimeError('Load has failed. Please examine the logs.')
else:
Utility.console_log('i = {}'.format(i))
Utility.console_log(str(self.p))
# This really should not happen. TIMEOUT and EOF failures are exceptions.
raise RuntimeError('An unknown error has occurred. Please examine the logs.')
self.p.expect_exact('(gdb)')
def close(self):
try:
self.p.sendline('q')
self.p.expect_exact('Quit anyway? (y or n)')
self.p.sendline('y')
self.p.expect_exact('Ending remote debugging.')
except Exception:
Utility.console_log('gdb needs to be killed', 'O')
super(GDBProcess, self).close()
def break_till_end(self):
self.p.sendline('b esp_restart')
self.p.sendline('c')
self.p.expect_exact('Thread 1 hit Breakpoint 3, esp_restart ()')
class SerialThread(object):
def run(self, log_path, exit_event):
with serial.Serial('/dev/ttyUSB1', 115200) as ser, open(log_path, 'wb') as f:
@ -126,16 +41,33 @@ def test_examples_loadable_elf(env, extra_data):
example = ttfw_idf.LoadableElfExample(rel_project_path, app_files, target="esp32")
idf_path = example.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
sdkconfig = example.get_sdkconfig()
elf_path = os.path.join(example.binary_path, 'hello-world.elf')
esp_log_path = os.path.join(proj_path, 'esp.log')
assert(sdkconfig['CONFIG_IDF_TARGET_ESP32'] == 'y'), "Only ESP32 target is supported"
assert(sdkconfig['CONFIG_APP_BUILD_TYPE_ELF_RAM'] == 'y'), "ELF should be built with CONFIG_APP_BUILD_TYPE_ELF_RAM"
with SerialThread(esp_log_path):
with OCDProcess(proj_path), GDBProcess(proj_path, elf_path) as gdb:
gdb.break_till_end()
openocd_log = os.path.join(proj_path, 'openocd.log')
gdb_log = os.path.join(proj_path, 'gdb.log')
gdb_args = '-x {} --directory={}'.format(os.path.join(proj_path, '.gdbinit.ci'),
os.path.join(proj_path, 'main'))
with ttfw_idf.OCDProcess(openocd_log), ttfw_idf.GDBProcess(gdb_log, elf_path, gdb_args) as gdb:
gdb.pexpect_proc.sendline('') # it is for "---Type <return> to continue, or q <return> to quit---"
i = gdb.pexpect_proc.expect_exact(['Thread 1 hit Temporary breakpoint 2, app_main ()',
'Load failed'])
if i == 0:
Utility.console_log('gdb is at breakpoint')
elif i == 1:
raise RuntimeError('Load has failed. Please examine the logs.')
else:
Utility.console_log('i = {}'.format(i))
Utility.console_log(str(gdb.pexpect_proc))
# This really should not happen. TIMEOUT and EOF failures are exceptions.
raise RuntimeError('An unknown error has occurred. Please examine the logs.')
gdb.pexpect_proc.expect_exact('(gdb)')
gdb.pexpect_proc.sendline('b esp_restart')
gdb.pexpect_proc.sendline('c')
gdb.pexpect_proc.expect_exact('Thread 1 hit Breakpoint 3, esp_restart ()')
if pexpect.run('grep "Restarting now." {}'.format(esp_log_path), withexitstatus=True)[1]:
raise RuntimeError('Expected output from ESP was not received')

View file

@ -0,0 +1,43 @@
from tiny_test_fw import Utility
import random
import re
import time
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_nvs_rw_blob(env, extra_data):
dut = env.get_dut('nvs_rw_blob', 'examples/storage/nvs_rw_blob')
dut.start_app()
def expect_start_msg(index):
dut.expect_all('Restart counter = {}'.format(index),
'Run time:',
timeout=10)
expect_start_msg(0)
dut.expect('Nothing saved yet!', timeout=5)
nvs_store = []
for i in range(1, 10):
time.sleep(random.uniform(0.1, 2)) # in order to randomize the runtimes stored in NVS
try:
# Pulling GPIO0 low using DTR
dut.port_inst.setDTR(True)
dut.expect('Restarting...', timeout=5) # the application waits for a second
finally:
dut.port_inst.setDTR(False)
expect_start_msg(i)
dut.expect_all(*nvs_store, timeout=10)
Utility.console_log('Received: {}'.format(', '.join(nvs_store)))
new_runtime = dut.expect(re.compile(r'{}: (\d+)'.format(i)), timeout=10)[0]
nvs_store.append('{}: {}'.format(i, new_runtime))
Utility.console_log('loop {} has finished with runtime {}'.format(i, new_runtime))
if __name__ == '__main__':
test_examples_nvs_rw_blob()

View file

@ -0,0 +1,29 @@
from tiny_test_fw import Utility
import ttfw_idf
try:
from itertools import izip_longest as zip_longest
except ImportError:
# Python 3
from itertools import zip_longest
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_nvs_rw_value(env, extra_data):
dut = env.get_dut('nvs_rw_value', 'examples/storage/nvs_rw_value')
dut.start_app()
for i, counter_state in zip_longest(range(4), ('The value is not initialized yet!', ), fillvalue='Done'):
dut.expect_all('Opening Non-Volatile Storage (NVS) handle... Done',
'Reading restart counter from NVS ... {}'.format(counter_state),
'Restart counter = {}'.format(i) if i > 0 else '',
'Updating restart counter in NVS ... Done',
'Committing updates in NVS ... Done',
'Restarting in 10 seconds...',
timeout=20)
Utility.console_log('loop {} has finished'.format(i))
if __name__ == '__main__':
test_examples_nvs_rw_value()

View file

@ -0,0 +1,29 @@
from tiny_test_fw import Utility
import ttfw_idf
try:
from itertools import izip_longest as zip_longest
except ImportError:
# Python 3
from itertools import zip_longest
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_nvs_rw_value_cxx(env, extra_data):
dut = env.get_dut('nvs_rw_value_cxx', 'examples/storage/nvs_rw_value_cxx')
dut.start_app()
for i, counter_state in zip_longest(range(4), ('The value is not initialized yet!', ), fillvalue='Done'):
dut.expect_all('Opening Non-Volatile Storage (NVS) handle... Done',
'Reading restart counter from NVS ... {}'.format(counter_state),
'Restart counter = {}'.format(i) if i > 0 else '',
'Updating restart counter in NVS ... Done',
'Committing updates in NVS ... Done',
'Restarting in 10 seconds...',
timeout=20)
Utility.console_log('loop {} has finished'.format(i))
if __name__ == '__main__':
test_examples_nvs_rw_value_cxx()

View file

@ -0,0 +1,45 @@
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_partition_find(env, extra_data):
dut = env.get_dut('partition_find', 'examples/storage/partition_api/partition_find')
dut.start_app()
def expect_partition(name, offset, size):
dut.expect("found partition '{}' at offset {:#x} with size {:#x}".format(name, offset, size), timeout=5)
def expect_find_partition(_type, subtype, label, name, offset, size):
dut.expect('Find partition with type {}, subtype {}, label {}'.format(_type, subtype, label), timeout=5)
expect_partition(name, offset, size)
dut.expect('----------------Find partitions---------------', timeout=20)
expect_find_partition('ESP_PARTITION_TYPE_DATA', 'ESP_PARTITION_SUBTYPE_DATA_NVS', 'NULL',
'nvs', 0x9000, 0x6000)
expect_find_partition('ESP_PARTITION_TYPE_DATA', 'ESP_PARTITION_SUBTYPE_DATA_PHY', 'NULL',
'phy_init', 0xf000, 0x1000)
expect_find_partition('ESP_PARTITION_TYPE_APP', 'ESP_PARTITION_SUBTYPE_APP_FACTORY', 'NULL',
'factory', 0x10000, 0x100000)
expect_find_partition('ESP_PARTITION_TYPE_DATA', 'ESP_PARTITION_SUBTYPE_DATA_FAT', 'NULL',
'storage1', 0x110000, 0x40000)
dut.expect('Find second FAT partition by specifying the label', timeout=5)
expect_find_partition('ESP_PARTITION_TYPE_DATA', 'ESP_PARTITION_SUBTYPE_DATA_FAT', 'storage2',
'storage2', 0x150000, 0x40000)
dut.expect_all('----------------Iterate through partitions---------------',
'Iterating through app partitions...', timeout=5)
expect_partition('factory', 0x10000, 0x100000)
dut.expect('Iterating through data partitions...', timeout=5)
expect_partition('nvs', 0x9000, 0x6000)
expect_partition('phy_init', 0xf000, 0x1000)
expect_partition('storage1', 0x110000, 0x40000)
expect_partition('storage2', 0x150000, 0x40000)
dut.expect('Example end', timeout=5)
if __name__ == '__main__':
test_examples_partition_find()

View file

@ -0,0 +1,22 @@
import re
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_partition_mmap(env, extra_data):
dut = env.get_dut('partition_mmap', 'examples/storage/partition_api/partition_mmap')
dut.start_app()
# ESP_ERROR_CHECK or assert will cause abort on error and "Example end" won't be received
dut.expect_all('Written sample data to partition: ESP-IDF Partition Memory Map Example',
re.compile(r'Mapped partition to data memory address \S+'),
'Read sample data from partition using mapped memory: ESP-IDF Partition Memory Map Example',
'Data matches',
'Unmapped partition from data memory',
'Example end',
timeout=20)
if __name__ == '__main__':
test_examples_partition_mmap()

View file

@ -0,0 +1,19 @@
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_partition_ops(env, extra_data):
dut = env.get_dut('partition_ops', 'examples/storage/partition_api/partition_ops')
dut.start_app()
# ESP_ERROR_CHECK or assert will cause abort on error and "Example end" won't be received
dut.expect_all('Written data: ESP-IDF Partition Operations Example (Read, Erase, Write)',
'Read data: ESP-IDF Partition Operations Example (Read, Erase, Write)',
'Erased data',
'Example end',
timeout=20)
if __name__ == '__main__':
test_examples_partition_ops()

View file

@ -120,7 +120,7 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example output
Here is an example console output. In this case a 128MB SDSC card was connected, and `format_if_mount_failed` parameter was set to `true` in the source code. Card was unformatted, so the initial mount has failed. Card was then partitioned, formatted, and mounted again.
Here is an example console output. In this case a 128MB SDSC card was connected, and `EXAMPLE_FORMAT_IF_MOUNT_FAILED` menuconfig option enabled. Card was unformatted, so the initial mount has failed. Card was then partitioned, formatted, and mounted again.
```
I (336) example: Initializing SD card
@ -165,6 +165,6 @@ Connections between the card and the ESP32 are too long for the frequency used.
### Failure to mount filesystem
```
example: Failed to mount filesystem. If you want the card to be formatted, set format_if_mount_failed = true.
example: Failed to mount filesystem. If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.
```
The example will be able to mount only cards formatted using FAT32 filesystem. If the card is formatted as exFAT or some other filesystem, you have an option to format it in the example code. Modify `format_if_mount_failed = false` to `format_if_mount_failed = true` in the example code, then build and flash the example.
The example will be able to mount only cards formatted using FAT32 filesystem. If the card is formatted as exFAT or some other filesystem, you have an option to format it in the example code. Enable the `EXAMPLE_FORMAT_IF_MOUNT_FAILED` menuconfig option, then build and flash the example.

View file

@ -0,0 +1,9 @@
menu "SD Card Example menu"
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
bool "Format the card if mount failed"
default n
help
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
the mount has failed.
endmenu

View file

@ -16,6 +16,7 @@
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "sdkconfig.h"
#ifdef CONFIG_IDF_TARGET_ESP32
#include "driver/sdmmc_host.h"
@ -66,7 +67,11 @@ void app_main(void)
// If format_if_mount_failed is set to true, SD card will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
@ -129,7 +134,7 @@ void app_main(void)
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the card to be formatted, set format_if_mount_failed = true.");
"If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));

View file

@ -0,0 +1,34 @@
from tiny_test_fw import Utility
import ttfw_idf
import re
@ttfw_idf.idf_example_test(env_tag='UT_T1_SDMODE')
def test_examples_sd_card(env, extra_data):
dut = env.get_dut('sd_card', 'examples/storage/sd_card')
dut.start_app()
dut.expect('example: Initializing SD card', timeout=20)
peripheral = dut.expect(re.compile(r'example: Using (\w+) peripheral'), timeout=5)[0]
Utility.console_log('peripheral {} detected'.format(peripheral))
assert peripheral in ('SDMMC', 'SPI')
# These lines are matched separately because of ASCII color codes in the output
name = dut.expect(re.compile(r'Name: (\w+)'), timeout=5)[0]
_type = dut.expect(re.compile(r'Type: (\S+)'), timeout=5)[0]
speed = dut.expect(re.compile(r'Speed: (\S+)'), timeout=5)[0]
size = dut.expect(re.compile(r'Size: (\S+)'), timeout=5)[0]
Utility.console_log('Card {} {} {}MHz {} found'.format(name, _type, speed, size))
dut.expect_all('Opening file',
'File written',
'Renaming file',
'Reading file',
"Read from file: 'Hello {}!".format(name),
'Card unmounted',
timeout=5)
if __name__ == '__main__':
test_examples_sd_card()

View file

@ -0,0 +1 @@
CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED=y

View file

@ -0,0 +1,58 @@
from io import open
import os
import shutil
import tempfile
import ttfw_idf
try:
from itertools import izip_longest as zip_longest
except ImportError:
# Python 3
from itertools import zip_longest
@ttfw_idf.idf_example_test(env_tag="test_jtag_arm")
def test_examples_semihost_vfs(env, extra_data):
rel_project_path = os.path.join('examples', 'storage', 'semihost_vfs')
dut = env.get_dut('semihost_vfs', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
host_file_name = 'host_file.txt'
try:
temp_dir = tempfile.mkdtemp()
host_file_path = os.path.join(proj_path, 'data', host_file_name)
shutil.copyfile(host_file_path, os.path.join(temp_dir, host_file_name))
openocd_extra_args = '-c \'set ESP_SEMIHOST_BASEDIR {}\''.format(temp_dir)
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log'), openocd_extra_args):
dut.start_app()
dut.expect_all('example: Switch to semihosted stdout',
'example: Switched back to UART stdout',
'example: Wrote 2798 bytes',
'====================== HOST DATA START =========================',
timeout=20)
with open(host_file_path) as f:
file_content = [line.strip() for line in f]
dut.expect_all(*file_content, timeout=20)
dut.expect_all('====================== HOST DATA END =========================',
'example: Read 6121 bytes',
timeout=5)
with open(os.path.join(temp_dir, 'esp32_stdout.txt')) as f:
def expected_content():
yield 'example: Switched to semihosted stdout'
for i in range(100):
yield 'Semihosted stdout write {}'.format(i)
yield 'example: Switch to UART stdout'
for actual, expected in zip_longest(f, expected_content(), fillvalue='-'):
if expected not in actual: # "in" used because of the printed ASCII color codes
raise RuntimeError('"{}" != "{}"'.format(expected, actual.strip()))
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
if __name__ == '__main__':
test_examples_semihost_vfs()

View file

@ -0,0 +1,22 @@
import re
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_spiffs(env, extra_data):
dut = env.get_dut('spiffs', 'examples/storage/spiffs')
dut.start_app()
dut.expect_all('example: Initializing SPIFFS',
re.compile(r'example: Partition size: total: \d+, used: \d+'),
'example: Opening file',
'example: File written',
'example: Renaming file',
'example: Reading file',
'example: Read from file: \'Hello World!\'',
'example: SPIFFS unmounted',
timeout=20)
if __name__ == '__main__':
test_examples_spiffs()

View file

@ -0,0 +1,21 @@
import re
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
def test_examples_wear_levelling(env, extra_data):
dut = env.get_dut('wear_levelling', 'examples/storage/wear_levelling')
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Opening file',
'example: File written',
'example: Reading file',
re.compile(r'example: Read from file: \'written using ESP-IDF \S+\''),
'example: Unmounting FAT filesystem',
'example: Done',
timeout=20)
if __name__ == '__main__':
test_examples_wear_levelling()

View file

@ -291,6 +291,7 @@ example_test_009:
when: always
paths:
- $CI_PROJECT_DIR/examples/get-started/hello_world/*.log
- $CI_PROJECT_DIR/examples/storage/semihost_vfs/*.log
expire_in: 1 week
variables:
SETUP_TOOLS: "1"
@ -339,6 +340,12 @@ example_test_012:
- ESP32
- Example_RMT_IR_PROTOCOLS
example_test_013:
extends: .example_test_template
tags:
- ESP32
- UT_T1_SDMODE
UT_001:
extends: .unit_test_template
parallel: 37

View file

@ -0,0 +1,82 @@
# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from tiny_test_fw import Utility
import pexpect
class CustomProcess(object):
def __init__(self, cmd, logfile, verbose):
self.verbose = verbose
self.f = open(logfile, 'wb')
if self.verbose:
Utility.console_log('Starting {} > {}'.format(cmd, self.f.name))
self.pexpect_proc = pexpect.spawn(cmd, timeout=60, logfile=self.f)
def __enter__(self):
return self
def close(self):
self.pexpect_proc.terminate(force=True)
def __exit__(self, type, value, traceback):
self.close()
self.f.close()
class OCDProcess(CustomProcess):
def __init__(self, logfile_path, extra_args='', verbose=True):
# TODO Use configuration file implied by the test environment (board)
cmd = 'openocd {} -f board/esp32-wrover-kit-3.3v.cfg'.format(extra_args)
super(OCDProcess, self).__init__(cmd, logfile_path, verbose)
patterns = ['Info : Listening on port 3333 for gdb connections']
try:
while True:
i = self.pexpect_proc.expect_exact(patterns, timeout=30)
# TIMEOUT or EOF exceptions will be thrown upon other errors
if i == 0:
if self.verbose:
Utility.console_log('openocd is listening for gdb connections')
break # success
except Exception:
if self.verbose:
Utility.console_log('openocd initialization has failed', 'R')
raise
def close(self):
try:
self.pexpect_proc.sendcontrol('c')
self.pexpect_proc.expect_exact('shutdown command invoked')
except Exception:
if self.verbose:
Utility.console_log('openocd needs to be killed', 'O')
super(OCDProcess, self).close()
class GDBProcess(CustomProcess):
def __init__(self, logfile_path, elffile_path, extra_args='', verbose=True):
cmd = 'xtensa-esp32-elf-gdb {} {}'.format(extra_args, elffile_path)
super(GDBProcess, self).__init__(cmd, logfile_path, verbose)
def close(self):
try:
self.pexpect_proc.sendline('q')
self.pexpect_proc.expect_exact('Quit anyway? (y or n)')
self.pexpect_proc.sendline('y')
self.pexpect_proc.expect_exact('Ending remote debugging.')
except Exception:
if self.verbose:
Utility.console_log('gdb needs to be killed', 'O')
super(GDBProcess, self).close()

View file

@ -17,6 +17,7 @@ import re
from tiny_test_fw import TinyFW, Utility
from .IDFApp import IDFApp, Example, LoadableElfExample, UT, TestApp # noqa: export all Apps for users
from .IDFDUT import IDFDUT, ESP32DUT, ESP32S2DUT, ESP8266DUT, ESP32QEMUDUT # noqa: export DUTs for users
from .DebugUtils import OCDProcess, GDBProcess # noqa: export DebugUtils for users
def format_case_id(chip, case_name):