feat: Add unit tests for new wrapper commands

This commit is contained in:
Jan Beran 2024-06-17 11:25:39 +02:00
parent a648a95660
commit b286105d5f
3 changed files with 204 additions and 15 deletions

View File

@ -1,4 +1,4 @@
[codespell]
skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*
ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane
ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn
write-changes = true

View File

@ -9,7 +9,6 @@ from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
import click
from idf_py_actions.global_options import global_options
@ -405,7 +404,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
if version:
generate_signing_key_args += ['--version', version]
if scheme:
generate_signing_key_args += ['--scheme', '2']
generate_signing_key_args += ['--scheme', scheme]
if extra_args['keyfile']:
generate_signing_key_args += [extra_args['keyfile']]
RunTool('espsecure', generate_signing_key_args, args.build_dir)()
@ -440,10 +439,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: Dict) -> List:
efuse_args = []
efuse_args += ['-p', args.port or get_default_serial_port()]
if args.baud:
efuse_args += ['-b', str(args.baud)]
if args.port:
efuse_args += ['-p', args.port]
elif not args.port and not extra_args['virt']: # if --virt, no port will be found and it would cause error
efuse_args += ['-p', get_default_serial_port()]
efuse_args += ['--chip', _get_project_desc(ctx, args)['target']]
if extra_args['virt']:
efuse_args += ['--virt']
if extra_args['before']:
efuse_args += ['--before', extra_args['before'].replace('-', '_')]
if extra_args['debug']:
@ -470,8 +472,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
burn_key_args += ['--force-write-always']
if extra_args['show_sensitive_info']:
burn_key_args += ['--show-sensitive-info']
if extra_args['image']:
burn_key_args.append(extra_args['image'])
if extra_args['efuse_positional_args']:
burn_key_args += extra_args['efuse_positional_args']
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) -> None:
@ -490,14 +492,14 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
read_protect_args += list(extra_args['efuse_positional_args'])
RunTool('espefuse', read_protect_args, args.build_dir)()
def efuse_summary(action: str, ctx: click.core.Context, args: PropertyDict, format: str, **extra_args: Tuple) -> None:
def efuse_summary(action: str, ctx: click.core.Context, args: PropertyDict, format: str, **extra_args: Dict) -> None:
ensure_build_directory(args, ctx.info_name)
summary_args = [PYTHON, '-m' 'espefuse', 'summary']
summary_args += _parse_efuse_args(ctx, args, extra_args)
if format:
summary_args += ['--format', format.replace('-', '_')]
if extra_args['efuses']:
summary_args += extra_args['efuse_name']
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:
@ -524,7 +526,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
}
]
EFUSE_OPTS = BAUD_AND_PORT + [
EFUSE_OPTS = [PORT] + [
{
'names': ['--virt'],
'is_flag': True,
'hidden': True,
'help': 'For host tests, the tool will work in the virtual mode (without connecting to a chip).',
},
{
'names': ['--before'],
'help': 'What to do before connecting to the chip.',
@ -802,6 +810,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'options': EFUSE_OPTS + [
{
'names': ['--no-protect-key'],
'is_flag': True,
'help': (
'Disable default read- and write-protecting of the key.'
'If this option is not set, once the key is flashed it cannot be read back or changed.'
@ -809,6 +818,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
},
{
'names': ['--force-write-always'],
'is_flag': True,
'help': (
"Write the eFuse even if it looks like it's already been written, or is write protected."
"Note that this option can't disable write protection, or clear any bit which has already been set."
@ -816,6 +826,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
},
{
'names': ['--show-sensitive-info'],
'is_flag': True,
'help': (
'Show data to be burned (may expose sensitive data). Enabled if --debug is used.'
),
@ -823,8 +834,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
],
'arguments': [
{
'names': ['image'],
'nargs': 1,
'names': ['efuse-positional-args'],
'nargs': -1,
},
],
},
@ -866,6 +877,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
{
'names': ['efuse-name'],
'nargs': 1,
'required': False,
},
],
},

View File

@ -360,5 +360,182 @@ class TestFileArgumentExpansion(TestCase):
self.assertIn('(expansion of @args_non_existent) could not be opened', cm.exception.output.decode('utf-8', 'ignore'))
class TestWrapperCommands(TestCase):
@classmethod
def setUpClass(cls):
cls.sample_project_dir = os.path.join(current_dir, '..', 'test_build_system', 'build_test_app')
os.chdir(cls.sample_project_dir)
super().setUpClass()
def call_command(self, command: List[str]) -> str:
try:
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}')
@classmethod
def tearDownClass(cls):
subprocess.run([sys.executable, idf_py_path, 'fullclean'], stdout=subprocess.DEVNULL)
os.chdir(current_dir)
super().tearDownClass()
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.
"""
def test_efuse_summary(self):
summary_command = [sys.executable, idf_py_path, 'efuse-summary', '--virt']
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'])
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'])
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('Successful', output)
output = self.call_command(burn_command + ['WR_DIS', '1', 'RD_DIS', '1'])
self.assertIn('WR_DIS', output)
self.assertIn('RD_DIS', output)
self.assertIn('Successful', output)
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)
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)])
self.assertIn('Burn keys to blocks:', output)
self.assertIn('Successful', output)
def test_efuse_dump(self):
dump_command = [sys.executable, idf_py_path, 'efuse-dump', '--virt']
output = self.call_command(dump_command)
self.assertIn('BLOCK0', output)
self.assertIn('BLOCK1', output)
self.assertIn('BLOCK2', output)
self.assertIn('BLOCK3', output)
self.assertIn('read_regs', output)
def test_efuse_read_protect(self):
read_protect_command = [sys.executable, idf_py_path, 'efuse-read-protect', '--virt', '--do-not-confirm']
output = self.call_command(read_protect_command + ['MAC_VERSION'])
self.assertIn('MAC_VERSION', output)
self.assertIn('Successful', output)
def test_efuse_write_protect(self):
write_protect_command = [sys.executable, idf_py_path, 'efuse-write-protect', '--virt', '--do-not-confirm']
output = self.call_command(write_protect_command + ['WR_DIS'])
self.assertIn('WR_DIS', output)
self.assertIn('Successful', output)
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.
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
subprocess.run([sys.executable, idf_py_path, 'build'], stdout=subprocess.DEVNULL)
cls.flash_encryption_key = 'test_key.bin'
cls.signing_key = 'test_signing_key.pem'
def secure_generate_flash_encryption_key(self):
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)
def secure_encrypt_flash_data(self):
self.secure_generate_flash_encryption_key()
encrypt_command = [sys.executable,
idf_py_path,
'secure-encrypt-flash-data',
'--aes-xts',
'--keyfile',
f'{self.flash_encryption_key}',
'--address',
'0x1000',
'--output',
'bootloader-enc.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,
idf_py_path,
'secure-decrypt-flash-data',
'--aes-xts',
'--keyfile',
f'{self.flash_encryption_key}',
'--address',
'0x1000',
'--output',
'bootloader-dec.bin',
'bootloader-enc.bin']
output = self.call_command(decrypt_command)
self.assertIn('Using 256-bit key', output)
self.assertIn('Done', output)
def secure_generate_signing_key(self):
generate_key_command = [sys.executable,
idf_py_path,
'secure-generate-signing-key',
'--version',
'2',
'--scheme',
'rsa3072',
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.assertIn('Done', output)
def secure_sign_data(self):
self.secure_generate_signing_key()
sign_command = [sys.executable,
idf_py_path,
'secure-sign-data',
'--version',
'2',
'--keyfile',
self.signing_key,
'--output',
'bootloader-signed.bin',
'bootloader/bootloader.bin']
output = self.call_command(sign_command)
self.assertIn('Signed', output)
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.
"""
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.assertIn(f'Merged binary {merged_binary_name} will be created in the build directory...', output)
if __name__ == '__main__':
main()