mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-03 18:40:59 +02:00
feat: Add support for esptool v5 and keep v4 for now
This commit is contained in:
@@ -36,11 +36,14 @@ def _test_flash_encryption(dut: Dut) -> None:
|
|||||||
key_bytes = b'\xff' + b'\x00' * 31
|
key_bytes = b'\xff' + b'\x00' * 31
|
||||||
aes_xts = True
|
aes_xts = True
|
||||||
|
|
||||||
# Emulate espsecure encrypt_flash_data command
|
|
||||||
EncryptFlashDataArgs = namedtuple(
|
EncryptFlashDataArgs = namedtuple(
|
||||||
'EncryptFlashDataArgs', ['output', 'plaintext_file', 'address', 'keyfile', 'flash_crypt_conf', 'aes_xts']
|
'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)
|
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)
|
espsecure.encrypt_flash_data(args)
|
||||||
|
|
||||||
expected_ciphertext = args.output.getvalue()
|
expected_ciphertext = args.output.getvalue()
|
||||||
|
@@ -38,7 +38,7 @@ def get_running_partition(port=None):
|
|||||||
try:
|
try:
|
||||||
# Check what esptool.py finds on what port the device is connected to
|
# 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
|
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())
|
pattern = re.compile(pattern.encode())
|
||||||
|
|
||||||
port = re.search(pattern, output).group(1) # may raise AttributeError
|
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
|
warning: unknown kconfig symbol 'WPA3_SAE' assigned to 'y' in .*/components/wpa_supplicant/test_apps/sdkconfig.defaults
|
||||||
ld: warning: ignoring duplicate libraries
|
ld: warning: ignoring duplicate libraries
|
||||||
archive library: .+ the table of contents is empty
|
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
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import click
|
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"""
|
"""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()
|
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:
|
def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any:
|
||||||
desc_path = os.path.join(args.build_dir, 'project_description.json')
|
desc_path = os.path.join(args.build_dir, 'project_description.json')
|
||||||
if not os.path.exists(desc_path):
|
if not os.path.exists(desc_path):
|
||||||
ensure_build_directory(args, ctx.info_name)
|
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)
|
project_desc = json.load(f)
|
||||||
return project_desc
|
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_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
|
||||||
esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
|
esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
|
||||||
if args.port is None:
|
if args.port is None:
|
||||||
@@ -84,7 +81,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
|||||||
result += ['--no-stub']
|
result += ['--no-stub']
|
||||||
return result
|
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"""
|
"""Return all the command line options up to first action"""
|
||||||
# This approach ignores argument parsing done Click
|
# This approach ignores argument parsing done Click
|
||||||
result = []
|
result = []
|
||||||
@@ -185,7 +182,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
|||||||
monitor_args += ['--disable-auto-color']
|
monitor_args += ['--disable-auto-color']
|
||||||
|
|
||||||
idf_py = [PYTHON] + _get_commandline_options(ctx) # commands to re-run idf.py
|
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
|
hints = not args.no_hints
|
||||||
|
|
||||||
# Temporally ignore SIGINT, which is used in idf_monitor to spawn gdb.
|
# 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']
|
esptool_args += ['erase_flash']
|
||||||
RunTool('esptool.py', esptool_args, args.build_dir, hints=not args.no_hints)()
|
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])
|
encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
|
||||||
if encryption:
|
if encryption:
|
||||||
for task in tasks:
|
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']]
|
encrypt_nvs_partition_args += [extra_args['partition_size']]
|
||||||
RunTool('espsecure', encrypt_nvs_partition_args, args.project_dir)()
|
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 = []
|
efuse_args = []
|
||||||
if args.port:
|
if args.port:
|
||||||
efuse_args += ['-p', 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']
|
efuse_args += ['--do-not-confirm']
|
||||||
return efuse_args
|
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)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
burn_efuse_args.append('burn_efuse')
|
||||||
if extra_args['efuse_positional_args']:
|
if extra_args['efuse_positional_args']:
|
||||||
burn_efuse_args += list(extra_args['efuse_positional_args'])
|
burn_efuse_args += list(extra_args['efuse_positional_args'])
|
||||||
RunTool('espefuse', burn_efuse_args, args.build_dir)()
|
RunTool('espefuse', burn_efuse_args, args.build_dir)()
|
||||||
|
|
||||||
def efuse_burn_key(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: str) -> None:
|
def efuse_burn_key(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: str) -> None:
|
||||||
ensure_build_directory(args, ctx.info_name)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
burn_key_args.append('burn_key')
|
||||||
if extra_args['no_protect_key']:
|
if extra_args['no_protect_key']:
|
||||||
burn_key_args += ['--no-protect-key']
|
burn_key_args += ['--no-protect-key']
|
||||||
if extra_args['force_write_always']:
|
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)()
|
RunTool('espefuse.py', burn_key_args, args.project_dir, build_dir=args.build_dir)()
|
||||||
|
|
||||||
def efuse_dump(
|
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:
|
) -> None:
|
||||||
ensure_build_directory(args, ctx.info_name)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
dump_args.append('dump')
|
||||||
if file_name:
|
if file_name:
|
||||||
dump_args += ['--file_name', file_name]
|
dump_args += ['--file_name', file_name]
|
||||||
RunTool('espefuse', dump_args, args.build_dir)()
|
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)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
read_protect_args.append('read_protect_efuse')
|
||||||
if extra_args['efuse_positional_args']:
|
if extra_args['efuse_positional_args']:
|
||||||
read_protect_args += list(extra_args['efuse_positional_args'])
|
read_protect_args += list(extra_args['efuse_positional_args'])
|
||||||
RunTool('espefuse', read_protect_args, args.build_dir)()
|
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,
|
ctx: click.core.Context,
|
||||||
args: PropertyDict,
|
args: PropertyDict,
|
||||||
format: str, # noqa: A002
|
format: str, # noqa: A002
|
||||||
**extra_args: Dict,
|
**extra_args: dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
ensure_build_directory(args, ctx.info_name)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
summary_args.append('summary')
|
||||||
if format:
|
if format:
|
||||||
summary_args += [f'--format={format.replace("-", "_")}']
|
summary_args += [f'--format={format.replace("-", "_")}']
|
||||||
if extra_args['efuse_name']:
|
if extra_args['efuse_name']:
|
||||||
summary_args += [str(extra_args['efuse_name'])]
|
summary_args += [str(extra_args['efuse_name'])]
|
||||||
RunTool('espefuse', summary_args, args.build_dir)()
|
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)
|
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 += _parse_efuse_args(ctx, args, extra_args)
|
||||||
|
write_protect_args.append('write_protect_efuse')
|
||||||
if extra_args['efuse_positional_args']:
|
if extra_args['efuse_positional_args']:
|
||||||
write_protect_args += list(extra_args['efuse_positional_args'])
|
write_protect_args += list(extra_args['efuse_positional_args'])
|
||||||
RunTool('espefuse', write_protect_args, args.build_dir)()
|
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
|
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Optional
|
from collections import namedtuple
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import esptool
|
import esptool
|
||||||
from pytest_embedded_idf.app import IdfApp
|
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):
|
class LoadableAppSerial(EspSerial):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
app: IdfApp,
|
app: IdfApp,
|
||||||
target: Optional[str] = None,
|
target: str | None = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
if not hasattr(self.app, 'target'):
|
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:
|
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}.')
|
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...')
|
logging.error('No image file detected. Skipping load ram...')
|
||||||
return
|
return
|
||||||
|
|
||||||
f_bin_file = open(self.app.bin_file, 'rb')
|
with open(self.app.bin_file, 'rb') as f_bin_file:
|
||||||
|
|
||||||
default_kwargs = {
|
|
||||||
'filename': f_bin_file,
|
|
||||||
'chip': self.esp.CHIP_NAME.lower().replace('-', ''),
|
|
||||||
}
|
|
||||||
|
|
||||||
load_ram_args = EsptoolArgs(**default_kwargs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.esp.change_baud(460800)
|
self.esp.change_baud(460800)
|
||||||
esptool.load_ram(self.esp, load_ram_args)
|
try:
|
||||||
finally:
|
# esptool v5.0+
|
||||||
f_bin_file.close()
|
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
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from test_build_system_helpers import APP_BINS
|
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
|
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:
|
for path in paths:
|
||||||
assert os.path.exists(path)
|
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')
|
logging.info('Can build with IDF_PATH set via cmake cache not environment')
|
||||||
replace_in_file('CMakeLists.txt', 'ENV{IDF_PATH}', '{IDF_PATH}')
|
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)
|
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||||
idf_py('fullclean')
|
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
|
# working with already changed CMakeLists.txt
|
||||||
kconfig_file = test_app_copy / 'main' / 'Kconfig.projbuild'
|
kconfig_file = test_app_copy / 'main' / 'Kconfig.projbuild'
|
||||||
kconfig_file.write_text('source "$IDF_PATH/examples/wifi/getting_started/station/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)
|
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
||||||
kconfig_file.unlink() # remove file to not affect following sub-test
|
kconfig_file.unlink() # remove file to not affect following sub-test
|
||||||
idf_py('fullclean')
|
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')
|
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
|
# 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}')
|
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)
|
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:
|
def test_build_uf2(idf_py: IdfPyFunc) -> None:
|
||||||
logging.info('UF2 build works')
|
logging.info('UF2 build works')
|
||||||
ret = idf_py('uf2')
|
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'])
|
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/uf2.bin'])
|
||||||
ret = idf_py('uf2-app')
|
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'
|
'UF2 build should work for application binary'
|
||||||
)
|
)
|
||||||
assert_built(['build/uf2-app.bin'])
|
assert_built(['build/uf2-app.bin'])
|
||||||
idf_py('set-target', 'esp32s2')
|
idf_py('set-target', 'esp32s2')
|
||||||
ret = idf_py('uf2')
|
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'
|
'UF2 build should work for esp32s2'
|
||||||
)
|
)
|
||||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN + ['build/uf2.bin'])
|
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')
|
ret = idf_py('build')
|
||||||
assert " file should be named 'Kconfig.projbuild'" in ret.stderr, 'Misspelled Kconfig file should be detected'
|
assert " file should be named 'Kconfig.projbuild'" in ret.stderr, 'Misspelled Kconfig file should be detected'
|
||||||
assert_built(BOOTLOADER_BINS + APP_BINS + PARTITION_BIN)
|
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()
|
sdkconfig = f.read()
|
||||||
assert 'CONFIG_FROM_MISSPELLED_KCONFIG=y' in sdkconfig, (
|
assert 'CONFIG_FROM_MISSPELLED_KCONFIG=y' in sdkconfig, (
|
||||||
'There should be a config from the misspelled Kconfig file in sdkconfig'
|
'There should be a config from the misspelled Kconfig file in sdkconfig'
|
||||||
|
@@ -8,10 +8,9 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import List
|
from unittest import TestCase
|
||||||
from unittest import main
|
from unittest import main
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
@@ -37,10 +36,13 @@ class TestWithoutExtensions(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
# Disable the component manager and extra extensions for these tests
|
# 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_COMPONENT_MANAGER': '0',
|
||||||
'IDF_EXTRA_ACTIONS_PATH': '',
|
'IDF_EXTRA_ACTIONS_PATH': '',
|
||||||
})
|
},
|
||||||
|
)
|
||||||
cls.env_patcher.start()
|
cls.env_patcher.start()
|
||||||
|
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
@@ -51,8 +53,9 @@ class TestExtensions(TestWithoutExtensions):
|
|||||||
try:
|
try:
|
||||||
os.symlink(extension_path, link_path)
|
os.symlink(extension_path, link_path)
|
||||||
os.environ['IDF_EXTRA_ACTIONS_PATH'] = os.path.join(current_dir, 'extra_path')
|
os.environ['IDF_EXTRA_ACTIONS_PATH'] = os.path.join(current_dir, 'extra_path')
|
||||||
output = subprocess.check_output([sys.executable, idf_py_path, '--help'],
|
output = subprocess.check_output([sys.executable, idf_py_path, '--help'], env=os.environ).decode(
|
||||||
env=os.environ).decode('utf-8', 'ignore')
|
'utf-8', 'ignore'
|
||||||
|
)
|
||||||
|
|
||||||
self.assertIn('--test-extension-option', output)
|
self.assertIn('--test-extension-option', output)
|
||||||
self.assertIn('test_subcommand', 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')])
|
os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_path')])
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
[sys.executable, idf_py_path, '--some-extension-option=awesome', 'test_subcommand', 'extra_subcommand'],
|
[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 global callback: awesome', output)
|
||||||
self.assertIn('!!! From some subcommand', output)
|
self.assertIn('!!! From some subcommand', output)
|
||||||
self.assertIn('!!! From test global callback: test', output)
|
self.assertIn('!!! From test global callback: test', output)
|
||||||
@@ -79,8 +83,9 @@ class TestExtensions(TestWithoutExtensions):
|
|||||||
try:
|
try:
|
||||||
os.symlink(extension_path, link_path)
|
os.symlink(extension_path, link_path)
|
||||||
os.environ['IDF_EXTRA_ACTIONS_PATH'] = ';'.join([os.path.join(current_dir, 'extra_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'],
|
output = subprocess.check_output([sys.executable, idf_py_path, '--help'], env=os.environ).decode(
|
||||||
env=os.environ).decode('utf-8', 'ignore')
|
'utf-8', 'ignore'
|
||||||
|
)
|
||||||
self.assertIn('test_subcommand', output)
|
self.assertIn('test_subcommand', output)
|
||||||
self.assertNotIn('hidden_one', output)
|
self.assertNotIn('hidden_one', output)
|
||||||
|
|
||||||
@@ -127,7 +132,8 @@ class TestDependencyManagement(TestWithoutExtensions):
|
|||||||
sys.stderr = sys.__stderr__
|
sys.stderr = sys.__stderr__
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'WARNING: Commands "all", "clean" are found in the list of commands more than once.',
|
'WARNING: Commands "all", "clean" are found in the list of commands more than once.',
|
||||||
capturedOutput.getvalue())
|
capturedOutput.getvalue(),
|
||||||
|
)
|
||||||
|
|
||||||
sys.stderr = capturedOutput
|
sys.stderr = capturedOutput
|
||||||
idf.init_cli()(
|
idf.init_cli()(
|
||||||
@@ -136,7 +142,8 @@ class TestDependencyManagement(TestWithoutExtensions):
|
|||||||
)
|
)
|
||||||
sys.stderr = sys.__stderr__
|
sys.stderr = sys.__stderr__
|
||||||
self.assertIn(
|
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):
|
class TestVerboseFlag(TestWithoutExtensions):
|
||||||
@@ -145,10 +152,13 @@ class TestVerboseFlag(TestWithoutExtensions):
|
|||||||
[
|
[
|
||||||
sys.executable,
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'-C%s' % current_dir,
|
'-C',
|
||||||
|
current_dir,
|
||||||
'-v',
|
'-v',
|
||||||
'test-verbose',
|
'test-verbose',
|
||||||
], env=os.environ).decode('utf-8', 'ignore')
|
],
|
||||||
|
env=os.environ,
|
||||||
|
).decode('utf-8', 'ignore')
|
||||||
|
|
||||||
self.assertIn('Verbose mode on', output)
|
self.assertIn('Verbose mode on', output)
|
||||||
|
|
||||||
@@ -157,9 +167,12 @@ class TestVerboseFlag(TestWithoutExtensions):
|
|||||||
[
|
[
|
||||||
sys.executable,
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'-C%s' % current_dir,
|
'-C',
|
||||||
|
current_dir,
|
||||||
'test-verbose',
|
'test-verbose',
|
||||||
], env=os.environ).decode('utf-8', 'ignore')
|
],
|
||||||
|
env=os.environ,
|
||||||
|
).decode('utf-8', 'ignore')
|
||||||
|
|
||||||
self.assertIn('Output from test-verbose', output)
|
self.assertIn('Output from test-verbose', output)
|
||||||
self.assertNotIn('Verbose mode on', output)
|
self.assertNotIn('Verbose mode on', output)
|
||||||
@@ -188,27 +201,31 @@ class TestDeprecations(TestWithoutExtensions):
|
|||||||
def test_exit_with_error_for_subcommand(self):
|
def test_exit_with_error_for_subcommand(self):
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(
|
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:
|
except subprocess.CalledProcessError as e:
|
||||||
self.assertIn('Error: Command "test-2" is deprecated and was removed.', e.output.decode('utf-8', 'ignore'))
|
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):
|
def test_exit_with_error_for_option(self):
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(
|
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,
|
env=os.environ,
|
||||||
stderr=subprocess.STDOUT)
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Error: Option "test_5" is deprecated since v2.0 and was removed in v3.0.',
|
'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):
|
def test_deprecation_messages(self):
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
[
|
[
|
||||||
sys.executable,
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'-C%s' % current_dir,
|
'-C',
|
||||||
|
current_dir,
|
||||||
'--test-0=a',
|
'--test-0=a',
|
||||||
'--test-1=b',
|
'--test-1=b',
|
||||||
'--test-2=c',
|
'--test-2=c',
|
||||||
@@ -220,23 +237,28 @@ class TestDeprecations(TestWithoutExtensions):
|
|||||||
'test-1',
|
'test-1',
|
||||||
],
|
],
|
||||||
env=os.environ,
|
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: Option "test_sub_1" is deprecated and will be removed in future versions.', output)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Warning: Command "test-1" is deprecated and will be removed in future versions. '
|
'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_1" is deprecated and will be removed in future versions.', output)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Warning: Option "test_2" is deprecated and will be removed in future versions. '
|
'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.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)
|
||||||
self.assertNotIn('"test_0" is deprecated', output)
|
self.assertNotIn('"test_0" is deprecated', output)
|
||||||
|
|
||||||
|
|
||||||
class TestHelpOutput(TestWithoutExtensions):
|
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)
|
env = dict(**os.environ)
|
||||||
python = shutil.which('python', path=env['PATH'])
|
python = shutil.which('python', path=env['PATH'])
|
||||||
if python is None:
|
if python is None:
|
||||||
@@ -244,20 +266,17 @@ class TestHelpOutput(TestWithoutExtensions):
|
|||||||
idf_path = env.get('IDF_PATH')
|
idf_path = env.get('IDF_PATH')
|
||||||
if idf_path is None:
|
if idf_path is None:
|
||||||
raise ValueError('Empty IDF_PATH')
|
raise ValueError('Empty IDF_PATH')
|
||||||
idf_py_cmd = [
|
idf_py_cmd = [python, os.path.join(idf_path, 'tools', 'idf.py')]
|
||||||
python,
|
|
||||||
os.path.join(idf_path, 'tools', 'idf.py')
|
|
||||||
]
|
|
||||||
commands = idf_py_cmd + commands
|
commands = idf_py_cmd + commands
|
||||||
output_file = 'idf_py_help_output.json'
|
output_file = 'idf_py_help_output.json'
|
||||||
with open(output_file, 'w') as outfile:
|
with open(output_file, 'w') as outfile:
|
||||||
subprocess.run(commands, env=env, stdout=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)
|
help_obj = json.load(outfile)
|
||||||
self.assertIsNone(jsonschema.validate(help_obj, schema))
|
self.assertIsNone(jsonschema.validate(help_obj, schema))
|
||||||
|
|
||||||
def test_output(self):
|
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)
|
schema_json = json.load(schema_file)
|
||||||
self.action_test_idf_py(['help', '--json'], schema_json)
|
self.action_test_idf_py(['help', '--json'], schema_json)
|
||||||
self.action_test_idf_py(['help', '--json', '--add-options'], schema_json)
|
self.action_test_idf_py(['help', '--json', '--add-options'], schema_json)
|
||||||
@@ -270,7 +289,8 @@ class TestFileArgumentExpansion(TestCase):
|
|||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_a'],
|
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_a'],
|
||||||
env=os.environ,
|
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)
|
self.assertIn('Running: idf.py --version DAAA DBBB', output)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
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"""
|
"""Test multiple @filename arguments"""
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(
|
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,
|
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)
|
self.assertIn('Running: idf.py --version DAAA DBBB DCCC DDDD', output)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
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(
|
output = subprocess.check_output(
|
||||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_recursive'],
|
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_recursive'],
|
||||||
env=os.environ,
|
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)
|
self.assertIn('Running: idf.py --version DAAA DBBB DEEE DFFF', output)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
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(
|
subprocess.check_output(
|
||||||
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_circular_a'],
|
[sys.executable, idf_py_path, '--version', '@file_args_expansion_inputs/args_circular_a'],
|
||||||
env=os.environ,
|
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'))
|
self.assertIn('Circular dependency in file argument expansion', cm.exception.output.decode('utf-8', 'ignore'))
|
||||||
|
|
||||||
def test_missing_file(self):
|
def test_missing_file(self):
|
||||||
@@ -312,8 +341,11 @@ class TestFileArgumentExpansion(TestCase):
|
|||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
[sys.executable, idf_py_path, '--version', '@args_non_existent'],
|
[sys.executable, idf_py_path, '--version', '@args_non_existent'],
|
||||||
env=os.environ,
|
env=os.environ,
|
||||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
stderr=subprocess.STDOUT,
|
||||||
self.assertIn('(expansion of @args_non_existent) could not be opened', cm.exception.output.decode('utf-8', 'ignore'))
|
).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):
|
class TestWrapperCommands(TestCase):
|
||||||
@@ -323,12 +355,11 @@ class TestWrapperCommands(TestCase):
|
|||||||
os.chdir(cls.sample_project_dir)
|
os.chdir(cls.sample_project_dir)
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
|
|
||||||
def call_command(self, command: List[str]) -> str:
|
def call_command(self, command: list[str]) -> str:
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(command, env=os.environ, stderr=subprocess.STDOUT).decode(
|
||||||
command,
|
'utf-8', 'ignore'
|
||||||
env=os.environ,
|
)
|
||||||
stderr=subprocess.STDOUT).decode('utf-8', 'ignore')
|
|
||||||
return output
|
return output
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.fail(f'Process should have exited normally, but it exited with a return code of {e.returncode}')
|
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):
|
class TestEFuseCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if wrapper commands for espefuse.py are working as expected.
|
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):
|
def test_efuse_summary(self):
|
||||||
@@ -351,17 +383,17 @@ class TestEFuseCommands(TestWrapperCommands):
|
|||||||
output = self.call_command(summary_command)
|
output = self.call_command(summary_command)
|
||||||
self.assertIn('EFUSE_NAME (Block) Description = [Meaningful Value] [Readable/Writeable] (Hex Value)', output)
|
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('00:00:00:00:00:00', output)
|
||||||
self.assertIn('MAC address', 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)
|
self.assertIn('0', output)
|
||||||
|
|
||||||
def test_efuse_burn(self):
|
def test_efuse_burn(self):
|
||||||
burn_command = [sys.executable, idf_py_path, 'efuse-burn', '--virt', '--do-not-confirm']
|
burn_command = [sys.executable, idf_py_path, 'efuse-burn', '--virt', '--do-not-confirm']
|
||||||
output = self.call_command(burn_command + ['WR_DIS', '1'])
|
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)
|
self.assertIn('Successful', output)
|
||||||
|
|
||||||
output = self.call_command(burn_command + ['WR_DIS', '1', 'RD_DIS', '1'])
|
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):
|
def test_efuse_burn_key(self):
|
||||||
key_name = 'efuse_test_key.bin'
|
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']
|
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('Burn keys to blocks:', output)
|
||||||
self.assertIn('Successful', output)
|
self.assertIn('Successful', output)
|
||||||
|
|
||||||
@@ -401,8 +438,10 @@ class TestEFuseCommands(TestWrapperCommands):
|
|||||||
class TestSecureCommands(TestWrapperCommands):
|
class TestSecureCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if wrapper commands for espsecure.py are working as expected.
|
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
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
@@ -412,13 +451,19 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
cls.nvs_partition_key = 'nvs_partition_key.bin'
|
cls.nvs_partition_key = 'nvs_partition_key.bin'
|
||||||
|
|
||||||
def secure_generate_flash_encryption_key(self):
|
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)
|
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):
|
def secure_encrypt_flash_data(self):
|
||||||
self.secure_generate_flash_encryption_key()
|
self.secure_generate_flash_encryption_key()
|
||||||
encrypt_command = [sys.executable,
|
encrypt_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-encrypt-flash-data',
|
'secure-encrypt-flash-data',
|
||||||
'--aes-xts',
|
'--aes-xts',
|
||||||
@@ -428,14 +473,16 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
'0x1000',
|
'0x1000',
|
||||||
'--output',
|
'--output',
|
||||||
'bootloader-enc.bin',
|
'bootloader-enc.bin',
|
||||||
'bootloader/bootloader.bin']
|
'bootloader/bootloader.bin',
|
||||||
|
]
|
||||||
output = self.call_command(encrypt_command)
|
output = self.call_command(encrypt_command)
|
||||||
self.assertIn('Using 256-bit key', output)
|
self.assertIn('Using 256-bit key', output)
|
||||||
self.assertIn('Done', output)
|
self.assertIn('Done', output)
|
||||||
|
|
||||||
def test_secure_decrypt_flash_data(self):
|
def test_secure_decrypt_flash_data(self):
|
||||||
self.secure_encrypt_flash_data()
|
self.secure_encrypt_flash_data()
|
||||||
decrypt_command = [sys.executable,
|
decrypt_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-decrypt-flash-data',
|
'secure-decrypt-flash-data',
|
||||||
'--aes-xts',
|
'--aes-xts',
|
||||||
@@ -445,14 +492,16 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
'0x1000',
|
'0x1000',
|
||||||
'--output',
|
'--output',
|
||||||
'bootloader-dec.bin',
|
'bootloader-dec.bin',
|
||||||
'bootloader-enc.bin']
|
'bootloader-enc.bin',
|
||||||
|
]
|
||||||
output = self.call_command(decrypt_command)
|
output = self.call_command(decrypt_command)
|
||||||
self.assertIn('Using 256-bit key', output)
|
self.assertIn('Using 256-bit key', output)
|
||||||
self.assertIn('Done', output)
|
self.assertIn('Done', output)
|
||||||
|
|
||||||
def secure_sign_data(self):
|
def secure_sign_data(self):
|
||||||
self.secure_generate_signing_key()
|
self.secure_generate_signing_key()
|
||||||
sign_command = [sys.executable,
|
sign_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-sign-data',
|
'secure-sign-data',
|
||||||
'--version',
|
'--version',
|
||||||
@@ -461,49 +510,57 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
f'../{self.signing_key}',
|
f'../{self.signing_key}',
|
||||||
'--output',
|
'--output',
|
||||||
'bootloader-signed.bin',
|
'bootloader-signed.bin',
|
||||||
'bootloader/bootloader.bin']
|
'bootloader/bootloader.bin',
|
||||||
|
]
|
||||||
output = self.call_command(sign_command)
|
output = self.call_command(sign_command)
|
||||||
self.assertIn('Signed', output)
|
self.assertIn('Signed', output)
|
||||||
|
|
||||||
def secure_verify_signature(self):
|
def secure_verify_signature(self):
|
||||||
self.secure_sign_data()
|
self.secure_sign_data()
|
||||||
sign_command = [sys.executable,
|
sign_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-verify-signature',
|
'secure-verify-signature',
|
||||||
'--version',
|
'--version',
|
||||||
'2',
|
'2',
|
||||||
'--keyfile',
|
'--keyfile',
|
||||||
f'../{self.signing_key}',
|
f'../{self.signing_key}',
|
||||||
'bootloader-signed.bin']
|
'bootloader-signed.bin',
|
||||||
|
]
|
||||||
output = self.call_command(sign_command)
|
output = self.call_command(sign_command)
|
||||||
self.assertIn('verification successful', output)
|
self.assertIn('verification successful', output)
|
||||||
|
|
||||||
def secure_generate_signing_key(self):
|
def secure_generate_signing_key(self):
|
||||||
generate_key_command = [sys.executable,
|
generate_key_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-generate-signing-key',
|
'secure-generate-signing-key',
|
||||||
'--version',
|
'--version',
|
||||||
'2',
|
'2',
|
||||||
'--scheme',
|
'--scheme',
|
||||||
'rsa3072',
|
'rsa3072',
|
||||||
self.signing_key]
|
self.signing_key,
|
||||||
|
]
|
||||||
output = self.call_command(generate_key_command)
|
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):
|
def test_secure_generate_key_digest(self):
|
||||||
self.secure_generate_signing_key()
|
self.secure_generate_signing_key()
|
||||||
digest_command = [sys.executable,
|
digest_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-generate-key-digest',
|
'secure-generate-key-digest',
|
||||||
'--keyfile',
|
'--keyfile',
|
||||||
f'{self.signing_key}',
|
f'{self.signing_key}',
|
||||||
'--output',
|
'--output',
|
||||||
'key_digest.bin']
|
'key_digest.bin',
|
||||||
|
]
|
||||||
output = self.call_command(digest_command)
|
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):
|
def test_secure_generate_nvs_partition_key(self):
|
||||||
generate_key_command = [sys.executable,
|
generate_key_command = [
|
||||||
|
sys.executable,
|
||||||
idf_py_path,
|
idf_py_path,
|
||||||
'secure-generate-nvs-partition-key',
|
'secure-generate-nvs-partition-key',
|
||||||
'--keyfile',
|
'--keyfile',
|
||||||
@@ -511,7 +568,8 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
'--encryption-scheme',
|
'--encryption-scheme',
|
||||||
'HMAC',
|
'HMAC',
|
||||||
'--hmac-keyfile',
|
'--hmac-keyfile',
|
||||||
'nvs_partition_key.bin']
|
'nvs_partition_key.bin',
|
||||||
|
]
|
||||||
output = self.call_command(generate_key_command)
|
output = self.call_command(generate_key_command)
|
||||||
self.assertIn('Created encryption keys:', output)
|
self.assertIn('Created encryption keys:', output)
|
||||||
|
|
||||||
@@ -519,14 +577,15 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
class TestMergeBinCommands(TestWrapperCommands):
|
class TestMergeBinCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if merge-bin command is invoked as expected.
|
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):
|
def test_merge_bin(self):
|
||||||
merge_bin_command = [sys.executable, idf_py_path, 'merge-bin']
|
merge_bin_command = [sys.executable, idf_py_path, 'merge-bin']
|
||||||
merged_binary_name = 'test-merge-binary.bin'
|
merged_binary_name = 'test-merge-binary.bin'
|
||||||
output = self.call_command(merge_bin_command + ['--output', merged_binary_name])
|
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)
|
self.assertIn(f'Merged binary {merged_binary_name} will be created in the build directory...', output)
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user