mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-03 18:40:59 +02:00
Merge branch 'feat/esptool_v5_minimal' into 'master'
Feat: Add minimal esptool v5 support See merge request espressif/esp-idf!41314
This commit is contained in:
@@ -36,11 +36,14 @@ def _test_flash_encryption(dut: Dut) -> None:
|
||||
key_bytes = b'\xff' + b'\x00' * 31
|
||||
aes_xts = True
|
||||
|
||||
# Emulate espsecure encrypt_flash_data command
|
||||
EncryptFlashDataArgs = namedtuple(
|
||||
'EncryptFlashDataArgs', ['output', 'plaintext_file', 'address', 'keyfile', 'flash_crypt_conf', 'aes_xts']
|
||||
)
|
||||
args = EncryptFlashDataArgs(BytesIO(), BytesIO(plain_data), flash_addr, BytesIO(key_bytes), 0xF, aes_xts)
|
||||
try:
|
||||
# espsecure 5.0 arguments are passed one by one; the following will convert tuple to dict and unwrap it
|
||||
espsecure.encrypt_flash_data(**args._asdict())
|
||||
except TypeError:
|
||||
espsecure.encrypt_flash_data(args)
|
||||
|
||||
expected_ciphertext = args.output.getvalue()
|
||||
|
@@ -38,7 +38,7 @@ def get_running_partition(port=None):
|
||||
try:
|
||||
# Check what esptool.py finds on what port the device is connected to
|
||||
output = subprocess.check_output([sys.executable, ESPTOOL_PY, 'chip_id']) # may raise CalledProcessError
|
||||
pattern = r'Serial port ([\S]+)'
|
||||
pattern = r'Serial port ([^:\s]+)'
|
||||
pattern = re.compile(pattern.encode())
|
||||
|
||||
port = re.search(pattern, output).group(1) # may raise AttributeError
|
||||
|
@@ -39,3 +39,8 @@ warning: unknown kconfig symbol 'UNITY_FREERTOS_STACK_SIZE' assigned to '12288'
|
||||
warning: unknown kconfig symbol 'WPA3_SAE' assigned to 'y' in .*/components/wpa_supplicant/test_apps/sdkconfig.defaults
|
||||
ld: warning: ignoring duplicate libraries
|
||||
archive library: .+ the table of contents is empty
|
||||
Warning: Deprecated: Option '--flash_size' is deprecated. Use '--flash-size' instead.
|
||||
Warning: Deprecated: Option '--flash_mode' is deprecated. Use '--flash-mode' instead.
|
||||
Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' instead.
|
||||
Warning: Deprecated: Command 'sign_data' is deprecated. Use 'sign-data' instead.
|
||||
Warning: Deprecated: Command 'extract_public_key' is deprecated. Use 'extract-public-key' instead.
|
||||
|
@@ -7,9 +7,6 @@ import signal
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
@@ -46,22 +43,22 @@ PORT = {
|
||||
}
|
||||
|
||||
|
||||
def yellow_print(message: str, newline: Optional[str] = '\n') -> None:
|
||||
def yellow_print(message: str, newline: str | None = '\n') -> None:
|
||||
"""Print a message to stderr with yellow highlighting"""
|
||||
sys.stderr.write('%s%s%s%s' % ('\033[0;33m', message, '\033[0m', newline))
|
||||
sys.stderr.write(f'\033[0;33m{message}\033[0m{newline}')
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any:
|
||||
desc_path = os.path.join(args.build_dir, 'project_description.json')
|
||||
if not os.path.exists(desc_path):
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
with open(desc_path, 'r', encoding='utf-8') as f:
|
||||
with open(desc_path, encoding='utf-8') as f:
|
||||
project_desc = json.load(f)
|
||||
return project_desc
|
||||
|
||||
def _get_esptool_args(args: PropertyDict) -> List:
|
||||
def _get_esptool_args(args: PropertyDict) -> list:
|
||||
esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
|
||||
esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
|
||||
if args.port is None:
|
||||
@@ -84,7 +81,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
result += ['--no-stub']
|
||||
return result
|
||||
|
||||
def _get_commandline_options(ctx: click.core.Context) -> List:
|
||||
def _get_commandline_options(ctx: click.core.Context) -> list:
|
||||
"""Return all the command line options up to first action"""
|
||||
# This approach ignores argument parsing done Click
|
||||
result = []
|
||||
@@ -185,7 +182,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
monitor_args += ['--disable-auto-color']
|
||||
|
||||
idf_py = [PYTHON] + _get_commandline_options(ctx) # commands to re-run idf.py
|
||||
monitor_args += ['-m', ' '.join("'%s'" % a for a in idf_py)]
|
||||
monitor_args += ['-m', ' '.join(f"'{a}'" for a in idf_py)]
|
||||
hints = not args.no_hints
|
||||
|
||||
# Temporally ignore SIGINT, which is used in idf_monitor to spawn gdb.
|
||||
@@ -247,7 +244,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
esptool_args += ['erase_flash']
|
||||
RunTool('esptool.py', esptool_args, args.build_dir, hints=not args.no_hints)()
|
||||
|
||||
def global_callback(ctx: click.core.Context, global_args: Dict, tasks: PropertyDict) -> None:
|
||||
def global_callback(ctx: click.core.Context, global_args: dict, tasks: PropertyDict) -> None:
|
||||
encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
|
||||
if encryption:
|
||||
for task in tasks:
|
||||
@@ -503,7 +500,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
encrypt_nvs_partition_args += [extra_args['partition_size']]
|
||||
RunTool('espsecure', encrypt_nvs_partition_args, args.project_dir)()
|
||||
|
||||
def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: Dict) -> List:
|
||||
def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: dict) -> list:
|
||||
efuse_args = []
|
||||
if args.port:
|
||||
efuse_args += ['-p', args.port]
|
||||
@@ -520,18 +517,20 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
efuse_args += ['--do-not-confirm']
|
||||
return efuse_args
|
||||
|
||||
def efuse_burn(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: Dict) -> None:
|
||||
def efuse_burn(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
burn_efuse_args = [PYTHON, '-mespefuse', 'burn_efuse']
|
||||
burn_efuse_args = [PYTHON, '-m', 'espefuse']
|
||||
burn_efuse_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
burn_efuse_args.append('burn_efuse')
|
||||
if extra_args['efuse_positional_args']:
|
||||
burn_efuse_args += list(extra_args['efuse_positional_args'])
|
||||
RunTool('espefuse', burn_efuse_args, args.build_dir)()
|
||||
|
||||
def efuse_burn_key(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: str) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
burn_key_args = [PYTHON, '-mespefuse', 'burn_key']
|
||||
burn_key_args = [PYTHON, '-m', 'espefuse']
|
||||
burn_key_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
burn_key_args.append('burn_key')
|
||||
if extra_args['no_protect_key']:
|
||||
burn_key_args += ['--no-protect-key']
|
||||
if extra_args['force_write_always']:
|
||||
@@ -543,19 +542,21 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
RunTool('espefuse.py', burn_key_args, args.project_dir, build_dir=args.build_dir)()
|
||||
|
||||
def efuse_dump(
|
||||
action: str, ctx: click.core.Context, args: PropertyDict, file_name: str, **extra_args: Dict
|
||||
action: str, ctx: click.core.Context, args: PropertyDict, file_name: str, **extra_args: dict
|
||||
) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
dump_args = [PYTHON, '-mespefuse', 'dump']
|
||||
dump_args = [PYTHON, '-m', 'espefuse']
|
||||
dump_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
dump_args.append('dump')
|
||||
if file_name:
|
||||
dump_args += ['--file_name', file_name]
|
||||
RunTool('espefuse', dump_args, args.build_dir)()
|
||||
|
||||
def efuse_read_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: Dict) -> None:
|
||||
def efuse_read_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
read_protect_args = [PYTHON, '-mespefuse', 'read_protect_efuse']
|
||||
read_protect_args = [PYTHON, '-m', 'espefuse']
|
||||
read_protect_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
read_protect_args.append('read_protect_efuse')
|
||||
if extra_args['efuse_positional_args']:
|
||||
read_protect_args += list(extra_args['efuse_positional_args'])
|
||||
RunTool('espefuse', read_protect_args, args.build_dir)()
|
||||
@@ -565,21 +566,23 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
ctx: click.core.Context,
|
||||
args: PropertyDict,
|
||||
format: str, # noqa: A002
|
||||
**extra_args: Dict,
|
||||
**extra_args: dict,
|
||||
) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
summary_args = [PYTHON, '-mespefuse', 'summary']
|
||||
summary_args = [PYTHON, '-m', 'espefuse']
|
||||
summary_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
summary_args.append('summary')
|
||||
if format:
|
||||
summary_args += [f'--format={format.replace("-", "_")}']
|
||||
if extra_args['efuse_name']:
|
||||
summary_args += [str(extra_args['efuse_name'])]
|
||||
RunTool('espefuse', summary_args, args.build_dir)()
|
||||
|
||||
def efuse_write_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: Dict) -> None:
|
||||
def efuse_write_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None:
|
||||
ensure_build_directory(args, ctx.info_name)
|
||||
write_protect_args = [PYTHON, '-mespefuse', 'write_protect_efuse']
|
||||
write_protect_args = [PYTHON, '-m', 'espefuse']
|
||||
write_protect_args += _parse_efuse_args(ctx, args, extra_args)
|
||||
write_protect_args.append('write_protect_efuse')
|
||||
if extra_args['efuse_positional_args']:
|
||||
write_protect_args += list(extra_args['efuse_positional_args'])
|
||||
RunTool('espefuse', write_protect_args, args.build_dir)()
|
||||
|
@@ -1,24 +1,25 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from collections import namedtuple
|
||||
from typing import Any
|
||||
|
||||
import esptool
|
||||
from pytest_embedded_idf.app import IdfApp
|
||||
from pytest_embedded_serial_esp.serial import EspSerial, EsptoolArgs
|
||||
from pytest_embedded_serial_esp.serial import EspSerial
|
||||
|
||||
|
||||
class LoadableAppSerial(EspSerial):
|
||||
def __init__(
|
||||
self,
|
||||
app: IdfApp,
|
||||
target: Optional[str] = None,
|
||||
target: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.app = app
|
||||
|
||||
if not hasattr(self.app, 'target'):
|
||||
raise ValueError(f'Idf app not parsable. Please check if it\'s valid: {self.app.binary_path}')
|
||||
raise ValueError(f"Idf app not parsable. Please check if it's valid: {self.app.binary_path}")
|
||||
|
||||
if target and self.app.target and self.app.target != target:
|
||||
raise ValueError(f'Targets do not match. App target: {self.app.target}, Cmd target: {target}.')
|
||||
@@ -37,17 +38,10 @@ class LoadableAppSerial(EspSerial):
|
||||
logging.error('No image file detected. Skipping load ram...')
|
||||
return
|
||||
|
||||
f_bin_file = open(self.app.bin_file, 'rb')
|
||||
|
||||
default_kwargs = {
|
||||
'filename': f_bin_file,
|
||||
'chip': self.esp.CHIP_NAME.lower().replace('-', ''),
|
||||
}
|
||||
|
||||
load_ram_args = EsptoolArgs(**default_kwargs)
|
||||
|
||||
try:
|
||||
with open(self.app.bin_file, 'rb') as f_bin_file:
|
||||
self.esp.change_baud(460800)
|
||||
esptool.load_ram(self.esp, load_ram_args)
|
||||
finally:
|
||||
f_bin_file.close()
|
||||
try:
|
||||
# esptool v5.0+
|
||||
esptool.load_ram(self.esp, input=f_bin_file)
|
||||
except TypeError:
|
||||
esptool.load_ram(self.esp, namedtuple('args', 'filename')(f_bin_file))
|
||||
|
@@ -2,13 +2,12 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from test_build_system_helpers import APP_BINS
|
||||
@@ -22,7 +21,7 @@ from test_build_system_helpers import replace_in_file
|
||||
from test_build_system_helpers import run_cmake_and_build
|
||||
|
||||
|
||||
def assert_built(paths: Union[List[str], List[Path]]) -> None:
|
||||
def assert_built(paths: list[str] | list[Path]) -> None:
|
||||
for path in paths:
|
||||
assert os.path.exists(path)
|
||||
|
||||
@@ -92,7 +91,7 @@ def test_build_with_cmake_and_idf_path_unset(idf_py: IdfPyFunc, test_app_copy: P
|
||||
|
||||
logging.info('Can build with IDF_PATH set via cmake cache not environment')
|
||||
replace_in_file('CMakeLists.txt', 'ENV{IDF_PATH}', '{IDF_PATH}')
|
||||
run_cmake_and_build('-G', 'Ninja', '..', '-DIDF_PATH={}'.format(idf_path), env=env)
|
||||
run_cmake_and_build('-G', 'Ninja', '..', f'-DIDF_PATH={idf_path}', env=env)
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||
idf_py('fullclean')
|
||||
|
||||
@@ -100,7 +99,7 @@ def test_build_with_cmake_and_idf_path_unset(idf_py: IdfPyFunc, test_app_copy: P
|
||||
# working with already changed CMakeLists.txt
|
||||
kconfig_file = test_app_copy / 'main' / 'Kconfig.projbuild'
|
||||
kconfig_file.write_text('source "$IDF_PATH/examples/wifi/getting_started/station/main/Kconfig.projbuild"')
|
||||
run_cmake_and_build('-G', 'Ninja', '..', '-DIDF_PATH={}'.format(idf_path), env=env)
|
||||
run_cmake_and_build('-G', 'Ninja', '..', f'-DIDF_PATH={idf_path}', env=env)
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||
kconfig_file.unlink() # remove file to not affect following sub-test
|
||||
idf_py('fullclean')
|
||||
@@ -108,7 +107,7 @@ def test_build_with_cmake_and_idf_path_unset(idf_py: IdfPyFunc, test_app_copy: P
|
||||
logging.info('Can build with IDF_PATH unset and inferred by build system')
|
||||
# replacing {IDF_PATH} not ENV{IDF_PATH} since CMakeLists.txt was already changed in this test
|
||||
replace_in_file('CMakeLists.txt', '{IDF_PATH}', '{ci_idf_path}')
|
||||
run_cmake_and_build('-G', 'Ninja', '-D', 'ci_idf_path={}'.format(idf_path), '..', env=env)
|
||||
run_cmake_and_build('-G', 'Ninja', '-D', f'ci_idf_path={idf_path}', '..', env=env)
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||
|
||||
|
||||
@@ -184,16 +183,18 @@ def test_build_dfu(idf_py: IdfPyFunc) -> None:
|
||||
def test_build_uf2(idf_py: IdfPyFunc) -> None:
|
||||
logging.info('UF2 build works')
|
||||
ret = idf_py('uf2')
|
||||
assert 'build/uf2.bin, ready to be flashed with any ESP USB Bridge' in ret.stdout, 'UF2 build should work for esp32'
|
||||
assert re.search(r"build/uf2.bin'?, ready to be flashed with any ESP USB Bridge", ret.stdout) is not None, (
|
||||
'UF2 build should work for esp32'
|
||||
)
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/uf2.bin'])
|
||||
ret = idf_py('uf2-app')
|
||||
assert 'build/uf2-app.bin, ready to be flashed with any ESP USB Bridge' in ret.stdout, (
|
||||
assert re.search(r"build/uf2-app.bin'?, ready to be flashed with any ESP USB Bridge", ret.stdout) is not None, (
|
||||
'UF2 build should work for application binary'
|
||||
)
|
||||
assert_built(['build/uf2-app.bin'])
|
||||
idf_py('set-target', 'esp32s2')
|
||||
ret = idf_py('uf2')
|
||||
assert 'build/uf2.bin, ready to be flashed with any ESP USB Bridge' in ret.stdout, (
|
||||
assert re.search(r"build/uf2.bin'?, ready to be flashed with any ESP USB Bridge", ret.stdout) is not None, (
|
||||
'UF2 build should work for esp32s2'
|
||||
)
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/uf2.bin'])
|
||||
@@ -253,7 +254,7 @@ def test_build_with_misspelled_kconfig(idf_py: IdfPyFunc, test_app_copy: Path) -
|
||||
ret = idf_py('build')
|
||||
assert " file should be named 'Kconfig.projbuild'" in ret.stderr, 'Misspelled Kconfig file should be detected'
|
||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||
with open(test_app_copy / 'sdkconfig', 'r') as f:
|
||||
with open(test_app_copy / 'sdkconfig') as f:
|
||||
sdkconfig = f.read()
|
||||
assert 'CONFIG_FROM_MISSPELLED_KCONFIG=y' in sdkconfig, (
|
||||
'There should be a config from the misspelled Kconfig file in sdkconfig'
|
||||
|
@@ -8,10 +8,9 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from unittest import TestCase
|
||||
from unittest import main
|
||||
from unittest import mock
|
||||
from unittest import TestCase
|
||||
|
||||
import jsonschema
|
||||
|
||||
@@ -37,10 +36,13 @@ class TestWithoutExtensions(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Disable the component manager and extra extensions for these tests
|
||||
cls.env_patcher = mock.patch.dict(os.environ, {
|
||||
cls.env_patcher = mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
'IDF_COMPONENT_MANAGER': '0',
|
||||
'IDF_EXTRA_ACTIONS_PATH': '',
|
||||
})
|
||||
},
|
||||
)
|
||||
cls.env_patcher.start()
|
||||
|
||||
super().setUpClass()
|
||||
@@ -51,8 +53,9 @@ class TestExtensions(TestWithoutExtensions):
|
||||
try:
|
||||
os.symlink(extension_path, link_path)
|
||||
os.environ['IDF_EXTRA_ACTIONS_PATH'] = os.path.join(current_dir, 'extra_path')
|
||||
output = subprocess.check_output([sys.executable, idf_py_path, '--help'],
|
||||
env=os.environ).decode('utf-8', 'ignore')
|
||||
output = subprocess.check_output([sys.executable, idf_py_path, '--help'], env=os.environ).decode(
|
||||
'utf-8', 'ignore'
|
||||
)
|
||||
|
||||
self.assertIn('--test-extension-option', output)
|
||||
self.assertIn('test_subcommand', output)
|
||||
@@ -67,7 +70,8 @@ class TestExtensions(TestWithoutExtensions):
|
||||
os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_path')])
|
||||
output = subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--some-extension-option=awesome', 'test_subcommand', 'extra_subcommand'],
|
||||
env=os.environ).decode('utf-8', 'ignore')
|
||||
env=os.environ,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn('!!! From some global callback: awesome', output)
|
||||
self.assertIn('!!! From some subcommand', output)
|
||||
self.assertIn('!!! From test global callback: test', output)
|
||||
@@ -79,8 +83,9 @@ class TestExtensions(TestWithoutExtensions):
|
||||
try:
|
||||
os.symlink(extension_path, link_path)
|
||||
os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_path')])
|
||||
output = subprocess.check_output([sys.executable, idf_py_path, '--help'],
|
||||
env=os.environ).decode('utf-8', 'ignore')
|
||||
output = subprocess.check_output([sys.executable, idf_py_path, '--help'], env=os.environ).decode(
|
||||
'utf-8', 'ignore'
|
||||
)
|
||||
self.assertIn('test_subcommand', output)
|
||||
self.assertNotIn('hidden_one', output)
|
||||
|
||||
@@ -127,7 +132,8 @@ class TestDependencyManagement(TestWithoutExtensions):
|
||||
sys.stderr = sys.__stderr__
|
||||
self.assertIn(
|
||||
'WARNING: Commands "all", "clean" are found in the list of commands more than once.',
|
||||
capturedOutput.getvalue())
|
||||
capturedOutput.getvalue(),
|
||||
)
|
||||
|
||||
sys.stderr = capturedOutput
|
||||
idf.init_cli()(
|
||||
@@ -136,7 +142,8 @@ class TestDependencyManagement(TestWithoutExtensions):
|
||||
)
|
||||
sys.stderr = sys.__stderr__
|
||||
self.assertIn(
|
||||
'WARNING: Command "clean" is found in the list of commands more than once.', capturedOutput.getvalue())
|
||||
'WARNING: Command "clean" is found in the list of commands more than once.', capturedOutput.getvalue()
|
||||
)
|
||||
|
||||
|
||||
class TestVerboseFlag(TestWithoutExtensions):
|
||||
@@ -145,10 +152,13 @@ class TestVerboseFlag(TestWithoutExtensions):
|
||||
[
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'-C%s' % current_dir,
|
||||
'-C',
|
||||
current_dir,
|
||||
'-v',
|
||||
'test-verbose',
|
||||
], env=os.environ).decode('utf-8', 'ignore')
|
||||
],
|
||||
env=os.environ,
|
||||
).decode('utf-8', 'ignore')
|
||||
|
||||
self.assertIn('Verbose mode on', output)
|
||||
|
||||
@@ -157,9 +167,12 @@ class TestVerboseFlag(TestWithoutExtensions):
|
||||
[
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'-C%s' % current_dir,
|
||||
'-C',
|
||||
current_dir,
|
||||
'test-verbose',
|
||||
], env=os.environ).decode('utf-8', 'ignore')
|
||||
],
|
||||
env=os.environ,
|
||||
).decode('utf-8', 'ignore')
|
||||
|
||||
self.assertIn('Output from test-verbose', output)
|
||||
self.assertNotIn('Verbose mode on', output)
|
||||
@@ -188,27 +201,31 @@ class TestDeprecations(TestWithoutExtensions):
|
||||
def test_exit_with_error_for_subcommand(self):
|
||||
try:
|
||||
subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '-C%s' % current_dir, 'test-2'], env=os.environ, stderr=subprocess.STDOUT)
|
||||
[sys.executable, idf_py_path, '-C', current_dir, 'test-2'], env=os.environ, stderr=subprocess.STDOUT
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.assertIn('Error: Command "test-2" is deprecated and was removed.', e.output.decode('utf-8', 'ignore'))
|
||||
|
||||
def test_exit_with_error_for_option(self):
|
||||
try:
|
||||
subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '-C%s' % current_dir, '--test-5=asdf'],
|
||||
[sys.executable, idf_py_path, '-C', current_dir, '--test-5=asdf'],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT)
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.assertIn(
|
||||
'Error: Option "test_5" is deprecated since v2.0 and was removed in v3.0.',
|
||||
e.output.decode('utf-8', 'ignore'))
|
||||
e.output.decode('utf-8', 'ignore'),
|
||||
)
|
||||
|
||||
def test_deprecation_messages(self):
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'-C%s' % current_dir,
|
||||
'-C',
|
||||
current_dir,
|
||||
'--test-0=a',
|
||||
'--test-1=b',
|
||||
'--test-2=c',
|
||||
@@ -220,23 +237,28 @@ class TestDeprecations(TestWithoutExtensions):
|
||||
'test-1',
|
||||
],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
|
||||
self.assertIn('Warning: Option "test_sub_1" is deprecated and will be removed in future versions.', output)
|
||||
self.assertIn(
|
||||
'Warning: Command "test-1" is deprecated and will be removed in future versions. '
|
||||
'Please use alternative command.', output)
|
||||
'Please use alternative command.',
|
||||
output,
|
||||
)
|
||||
self.assertIn('Warning: Option "test_1" is deprecated and will be removed in future versions.', output)
|
||||
self.assertIn(
|
||||
'Warning: Option "test_2" is deprecated and will be removed in future versions. '
|
||||
'Please update your parameters.', output)
|
||||
'Please update your parameters.',
|
||||
output,
|
||||
)
|
||||
self.assertIn('Warning: Option "test_3" is deprecated and will be removed in future versions.', output)
|
||||
self.assertNotIn('"test-0" is deprecated', output)
|
||||
self.assertNotIn('"test_0" is deprecated', output)
|
||||
|
||||
|
||||
class TestHelpOutput(TestWithoutExtensions):
|
||||
def action_test_idf_py(self, commands: List[str], schema: Any) -> None:
|
||||
def action_test_idf_py(self, commands: list[str], schema: Any) -> None:
|
||||
env = dict(**os.environ)
|
||||
python = shutil.which('python', path=env['PATH'])
|
||||
if python is None:
|
||||
@@ -244,20 +266,17 @@ class TestHelpOutput(TestWithoutExtensions):
|
||||
idf_path = env.get('IDF_PATH')
|
||||
if idf_path is None:
|
||||
raise ValueError('Empty IDF_PATH')
|
||||
idf_py_cmd = [
|
||||
python,
|
||||
os.path.join(idf_path, 'tools', 'idf.py')
|
||||
]
|
||||
idf_py_cmd = [python, os.path.join(idf_path, 'tools', 'idf.py')]
|
||||
commands = idf_py_cmd + commands
|
||||
output_file = 'idf_py_help_output.json'
|
||||
with open(output_file, 'w') as outfile:
|
||||
subprocess.run(commands, env=env, stdout=outfile)
|
||||
with open(output_file, 'r') as outfile:
|
||||
with open(output_file) as outfile:
|
||||
help_obj = json.load(outfile)
|
||||
self.assertIsNone(jsonschema.validate(help_obj, schema))
|
||||
|
||||
def test_output(self):
|
||||
with open(os.path.join(current_dir, 'idf_py_help_schema.json'), 'r') as schema_file:
|
||||
with open(os.path.join(current_dir, 'idf_py_help_schema.json')) as schema_file:
|
||||
schema_json = json.load(schema_file)
|
||||
self.action_test_idf_py(['help', '--json'], schema_json)
|
||||
self.action_test_idf_py(['help', '--json', '--add-options'], schema_json)
|
||||
@@ -270,7 +289,8 @@ class TestFileArgumentExpansion(TestCase):
|
||||
output = subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_a'],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn('Running: idf.py --version DAAA DBBB', output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
||||
@@ -279,9 +299,16 @@ class TestFileArgumentExpansion(TestCase):
|
||||
"""Test multiple @filename arguments"""
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_a', '@file_args_expansion_inputs/args_b'],
|
||||
[
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'--version',
|
||||
'@file_args_expansion_inputs/args_a',
|
||||
'@file_args_expansion_inputs/args_b',
|
||||
],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn('Running: idf.py --version DAAA DBBB DCCC DDDD', output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
||||
@@ -292,7 +319,8 @@ class TestFileArgumentExpansion(TestCase):
|
||||
output = subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_recursive'],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn('Running: idf.py --version DAAA DBBB DEEE DFFF', output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
||||
@@ -303,7 +331,8 @@ class TestFileArgumentExpansion(TestCase):
|
||||
subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_circular_a'],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn('Circular dependency in file argument expansion', cm.exception.output.decode('utf-8', 'ignore'))
|
||||
|
||||
def test_missing_file(self):
|
||||
@@ -312,8 +341,11 @@ class TestFileArgumentExpansion(TestCase):
|
||||
subprocess.check_output(
|
||||
[sys.executable, idf_py_path, '--version', '@args_non_existent'],
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
self.assertIn('(expansion of @args_non_existent) could not be opened', cm.exception.output.decode('utf-8', 'ignore'))
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8', 'ignore')
|
||||
self.assertIn(
|
||||
'(expansion of @args_non_existent) could not be opened', cm.exception.output.decode('utf-8', 'ignore')
|
||||
)
|
||||
|
||||
|
||||
class TestWrapperCommands(TestCase):
|
||||
@@ -323,12 +355,11 @@ class TestWrapperCommands(TestCase):
|
||||
os.chdir(cls.sample_project_dir)
|
||||
super().setUpClass()
|
||||
|
||||
def call_command(self, command: List[str]) -> str:
|
||||
def call_command(self, command: list[str]) -> str:
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
command,
|
||||
env=os.environ,
|
||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
||||
output = subprocess.check_output(command, env=os.environ, stderr=subprocess.STDOUT).decode(
|
||||
'utf-8', 'ignore'
|
||||
)
|
||||
return output
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
||||
@@ -343,7 +374,8 @@ class TestWrapperCommands(TestCase):
|
||||
class TestEFuseCommands(TestWrapperCommands):
|
||||
"""
|
||||
Test if wrapper commands for espefuse.py are working as expected.
|
||||
The goal is NOT to test the functionality of espefuse.py, but to test if the wrapper commands are working as expected.
|
||||
The goal is NOT to test the functionality of espefuse.py, but to test if the wrapper commands
|
||||
are working as expected.
|
||||
"""
|
||||
|
||||
def test_efuse_summary(self):
|
||||
@@ -351,17 +383,17 @@ class TestEFuseCommands(TestWrapperCommands):
|
||||
output = self.call_command(summary_command)
|
||||
self.assertIn('EFUSE_NAME (Block) Description = [Meaningful Value] [Readable/Writeable] (Hex Value)', output)
|
||||
|
||||
output = self.call_command(summary_command + ['--format','summary'])
|
||||
output = self.call_command(summary_command + ['--format', 'summary'])
|
||||
self.assertIn('00:00:00:00:00:00', output)
|
||||
self.assertIn('MAC address', output)
|
||||
|
||||
output = self.call_command(summary_command + ['--format','value-only', 'WR_DIS'])
|
||||
output = self.call_command(summary_command + ['--format', 'value-only', 'WR_DIS'])
|
||||
self.assertIn('0', output)
|
||||
|
||||
def test_efuse_burn(self):
|
||||
burn_command = [sys.executable, idf_py_path, 'efuse-burn', '--virt', '--do-not-confirm']
|
||||
output = self.call_command(burn_command + ['WR_DIS', '1'])
|
||||
self.assertIn('\'WR_DIS\' (Efuse write disable mask) 0x0000 -> 0x0001', output)
|
||||
self.assertIn("'WR_DIS' (Efuse write disable mask) 0x0000 -> 0x0001", output)
|
||||
self.assertIn('Successful', output)
|
||||
|
||||
output = self.call_command(burn_command + ['WR_DIS', '1', 'RD_DIS', '1'])
|
||||
@@ -371,9 +403,14 @@ class TestEFuseCommands(TestWrapperCommands):
|
||||
|
||||
def test_efuse_burn_key(self):
|
||||
key_name = 'efuse_test_key.bin'
|
||||
subprocess.run([sys.executable, idf_py_path, 'secure-generate-flash-encryption-key', os.path.join(current_dir, key_name)], stdout=subprocess.DEVNULL)
|
||||
subprocess.run(
|
||||
[sys.executable, idf_py_path, 'secure-generate-flash-encryption-key', os.path.join(current_dir, key_name)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
)
|
||||
burn_key_command = [sys.executable, idf_py_path, 'efuse-burn-key', '--virt', '--do-not-confirm']
|
||||
output = self.call_command(burn_key_command + ['--show-sensitive-info', 'secure_boot_v1', os.path.join(current_dir, key_name)])
|
||||
output = self.call_command(
|
||||
burn_key_command + ['--show-sensitive-info', 'secure_boot_v1', os.path.join(current_dir, key_name)]
|
||||
)
|
||||
self.assertIn('Burn keys to blocks:', output)
|
||||
self.assertIn('Successful', output)
|
||||
|
||||
@@ -401,8 +438,10 @@ class TestEFuseCommands(TestWrapperCommands):
|
||||
class TestSecureCommands(TestWrapperCommands):
|
||||
"""
|
||||
Test if wrapper commands for espsecure.py are working as expected.
|
||||
The goal is NOT to test the functionality of espsecure.py, but to test if the wrapper commands are working as expected.
|
||||
The goal is NOT to test the functionality of espsecure.py, but to test if the wrapper commands are
|
||||
working as expected.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
@@ -412,13 +451,19 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
cls.nvs_partition_key = 'nvs_partition_key.bin'
|
||||
|
||||
def secure_generate_flash_encryption_key(self):
|
||||
generate_key_command = [sys.executable, idf_py_path, 'secure-generate-flash-encryption-key', self.flash_encryption_key]
|
||||
generate_key_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-generate-flash-encryption-key',
|
||||
self.flash_encryption_key,
|
||||
]
|
||||
output = self.call_command(generate_key_command)
|
||||
self.assertIn(f'Writing 256 random bits to key file {self.flash_encryption_key}', output)
|
||||
self.assertRegex(output, f'Writing 256 random bits to key file "?{self.flash_encryption_key}"?')
|
||||
|
||||
def secure_encrypt_flash_data(self):
|
||||
self.secure_generate_flash_encryption_key()
|
||||
encrypt_command = [sys.executable,
|
||||
encrypt_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-encrypt-flash-data',
|
||||
'--aes-xts',
|
||||
@@ -428,14 +473,16 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
'0x1000',
|
||||
'--output',
|
||||
'bootloader-enc.bin',
|
||||
'bootloader/bootloader.bin']
|
||||
'bootloader/bootloader.bin',
|
||||
]
|
||||
output = self.call_command(encrypt_command)
|
||||
self.assertIn('Using 256-bit key', output)
|
||||
self.assertIn('Done', output)
|
||||
|
||||
def test_secure_decrypt_flash_data(self):
|
||||
self.secure_encrypt_flash_data()
|
||||
decrypt_command = [sys.executable,
|
||||
decrypt_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-decrypt-flash-data',
|
||||
'--aes-xts',
|
||||
@@ -445,14 +492,16 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
'0x1000',
|
||||
'--output',
|
||||
'bootloader-dec.bin',
|
||||
'bootloader-enc.bin']
|
||||
'bootloader-enc.bin',
|
||||
]
|
||||
output = self.call_command(decrypt_command)
|
||||
self.assertIn('Using 256-bit key', output)
|
||||
self.assertIn('Done', output)
|
||||
|
||||
def secure_sign_data(self):
|
||||
self.secure_generate_signing_key()
|
||||
sign_command = [sys.executable,
|
||||
sign_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-sign-data',
|
||||
'--version',
|
||||
@@ -461,49 +510,57 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
f'../{self.signing_key}',
|
||||
'--output',
|
||||
'bootloader-signed.bin',
|
||||
'bootloader/bootloader.bin']
|
||||
'bootloader/bootloader.bin',
|
||||
]
|
||||
output = self.call_command(sign_command)
|
||||
self.assertIn('Signed', output)
|
||||
|
||||
def secure_verify_signature(self):
|
||||
self.secure_sign_data()
|
||||
sign_command = [sys.executable,
|
||||
sign_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-verify-signature',
|
||||
'--version',
|
||||
'2',
|
||||
'--keyfile',
|
||||
f'../{self.signing_key}',
|
||||
'bootloader-signed.bin']
|
||||
'bootloader-signed.bin',
|
||||
]
|
||||
output = self.call_command(sign_command)
|
||||
self.assertIn('verification successful', output)
|
||||
|
||||
def secure_generate_signing_key(self):
|
||||
generate_key_command = [sys.executable,
|
||||
generate_key_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-generate-signing-key',
|
||||
'--version',
|
||||
'2',
|
||||
'--scheme',
|
||||
'rsa3072',
|
||||
self.signing_key]
|
||||
self.signing_key,
|
||||
]
|
||||
output = self.call_command(generate_key_command)
|
||||
self.assertIn(f'RSA 3072 private key in PEM format written to {self.signing_key}', output)
|
||||
self.assertRegex(output, f'RSA 3072 private key in PEM format written to "?{self.signing_key}"?')
|
||||
|
||||
def test_secure_generate_key_digest(self):
|
||||
self.secure_generate_signing_key()
|
||||
digest_command = [sys.executable,
|
||||
digest_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-generate-key-digest',
|
||||
'--keyfile',
|
||||
f'{self.signing_key}',
|
||||
'--output',
|
||||
'key_digest.bin']
|
||||
'key_digest.bin',
|
||||
]
|
||||
output = self.call_command(digest_command)
|
||||
self.assertIn(f'Writing the public key digest of {self.signing_key} to key_digest.bin', output)
|
||||
self.assertRegex(output, f'Writing the public key digest of "?{self.signing_key}"? to "?key_digest.bin"?.')
|
||||
|
||||
def test_secure_generate_nvs_partition_key(self):
|
||||
generate_key_command = [sys.executable,
|
||||
generate_key_command = [
|
||||
sys.executable,
|
||||
idf_py_path,
|
||||
'secure-generate-nvs-partition-key',
|
||||
'--keyfile',
|
||||
@@ -511,7 +568,8 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
'--encryption-scheme',
|
||||
'HMAC',
|
||||
'--hmac-keyfile',
|
||||
'nvs_partition_key.bin']
|
||||
'nvs_partition_key.bin',
|
||||
]
|
||||
output = self.call_command(generate_key_command)
|
||||
self.assertIn('Created encryption keys:', output)
|
||||
|
||||
@@ -519,14 +577,15 @@ class TestSecureCommands(TestWrapperCommands):
|
||||
class TestMergeBinCommands(TestWrapperCommands):
|
||||
"""
|
||||
Test if merge-bin command is invoked as expected.
|
||||
This test is not testing the functionality of esptool.py merge_bin command, but the invocation of the command from idf.py.
|
||||
This test is not testing the functionality of esptool.py merge_bin command, but the invocation of
|
||||
the command from idf.py.
|
||||
"""
|
||||
|
||||
def test_merge_bin(self):
|
||||
merge_bin_command = [sys.executable, idf_py_path, 'merge-bin']
|
||||
merged_binary_name = 'test-merge-binary.bin'
|
||||
output = self.call_command(merge_bin_command + ['--output', merged_binary_name])
|
||||
self.assertIn(f'file {merged_binary_name}, ready to flash to offset 0x0', output)
|
||||
self.assertRegex(output, f"file '?{merged_binary_name}'?, ready to flash to offset 0x0")
|
||||
self.assertIn(f'Merged binary {merged_binary_name} will be created in the build directory...', output)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user