1# Unit tests (really integration tests) for esptool.py using the pytest framework 2# Uses a device connected to the serial port. 3# 4# RUNNING THIS WILL MESS UP THE DEVICE'S SPI FLASH CONTENTS 5# 6# How to use: 7# 8# Run with a physical connection to a chip: 9# - `pytest test_esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200` 10# 11# where - --port - a serial port for esptool.py operation 12# - --chip - ESP chip name 13# - --baud - baud rate 14# - --with-trace - trace all interactions (True or False) 15 16import os 17import os.path 18import random 19import re 20import struct 21import subprocess 22import sys 23import tempfile 24import time 25from socket import AF_INET, SOCK_STREAM, socket 26from time import sleep 27from typing import List 28from unittest.mock import MagicMock 29 30# Link command line options --port, --chip, --baud, --with-trace, and --preload-port 31from conftest import ( 32 arg_baud, 33 arg_chip, 34 arg_port, 35 arg_preload_port, 36 arg_trace, 37 need_to_install_package_err, 38) 39 40 41import pytest 42 43try: 44 import esptool 45 import espefuse 46except ImportError: 47 need_to_install_package_err() 48 49import serial 50 51 52TEST_DIR = os.path.abspath(os.path.dirname(__file__)) 53 54# esptool.py skips strapping mode check in USB-CDC case if this is set 55os.environ["ESPTOOL_TESTING"] = "1" 56 57print("Running esptool.py tests...") 58 59 60class ESPRFC2217Server(object): 61 """Creates a virtual serial port accessible through rfc2217 port.""" 62 63 def __init__(self, rfc2217_port=None): 64 self.port = rfc2217_port or self.get_free_port() 65 self.cmd = [ 66 sys.executable, 67 os.path.join(TEST_DIR, "..", "esp_rfc2217_server.py"), 68 "-p", 69 str(self.port), 70 arg_port, 71 ] 72 self.server_output_file = open(f"{TEST_DIR}/{str(arg_chip)}_server.out", "a") 73 self.server_output_file.write("************************************") 74 self.p = None 75 self.wait_for_server_starts(attempts_count=5) 76 77 @staticmethod 78 def get_free_port(): 79 s = socket(AF_INET, SOCK_STREAM) 80 s.bind(("", 0)) 81 port = s.getsockname()[1] 82 s.close() 83 return port 84 85 def wait_for_server_starts(self, attempts_count): 86 for attempt in range(attempts_count): 87 try: 88 self.p = subprocess.Popen( 89 self.cmd, 90 cwd=TEST_DIR, 91 stdout=self.server_output_file, 92 stderr=subprocess.STDOUT, 93 close_fds=True, 94 ) 95 sleep(2) 96 s = socket(AF_INET, SOCK_STREAM) 97 result = s.connect_ex(("localhost", self.port)) 98 s.close() 99 if result == 0: 100 print("Server started successfully.") 101 return 102 except Exception as e: 103 print(e) 104 print( 105 "Server start failed." 106 + (" Retrying . . ." if attempt < attempts_count - 1 else "") 107 ) 108 self.p.terminate() 109 raise Exception("Server not started successfully!") 110 111 def __enter__(self): 112 return self 113 114 def __exit__(self, type, value, traceback): 115 self.server_output_file.close() 116 self.p.terminate() 117 118 119# Re-run all tests at least once if failure happens in USB-JTAG/Serial 120@pytest.mark.flaky(reruns=1, condition=arg_preload_port is not False) 121class EsptoolTestCase: 122 def run_espsecure(self, args): 123 cmd = [sys.executable, "-m", "espsecure"] + args.split(" ") 124 print("\nExecuting {}...".format(" ".join(cmd))) 125 try: 126 output = subprocess.check_output( 127 [str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT 128 ) 129 output = output.decode("utf-8") 130 print(output) # for more complete stdout logs on failure 131 return output 132 except subprocess.CalledProcessError as e: 133 print(e.output) 134 raise e 135 136 def run_esptool(self, args, baud=None, chip=None, port=None, preload=True): 137 """ 138 Run esptool with the specified arguments. --chip, --port and --baud 139 are filled in automatically from the command line. 140 (These can be overridden with their respective params.) 141 142 Additional args passed in args parameter as a string. 143 144 Preloads a dummy binary if --preload_port is specified. 145 This is needed in USB-JTAG/Serial mode to disable the 146 RTC watchdog, which causes the port to periodically disappear. 147 148 Returns output from esptool.py as a string if there is any. 149 Raises an exception if esptool.py fails. 150 """ 151 152 def run_esptool_process(cmd): 153 print("Executing {}...".format(" ".join(cmd))) 154 try: 155 output = subprocess.check_output( 156 [str(s) for s in cmd], 157 cwd=TEST_DIR, 158 stderr=subprocess.STDOUT, 159 ) 160 return output.decode("utf-8") 161 except subprocess.CalledProcessError as e: 162 print(e.output.decode("utf-8")) 163 raise e 164 165 try: 166 # Used for flasher_stub/run_tests_with_stub.sh 167 esptool = [os.environ["ESPTOOL_PY"]] 168 except KeyError: 169 # Run the installed esptool module 170 esptool = ["-m", "esptool"] 171 trace_arg = ["--trace"] if arg_trace else [] 172 base_cmd = [sys.executable] + esptool + trace_arg 173 if chip or arg_chip is not None and chip != "auto": 174 base_cmd += ["--chip", chip or arg_chip] 175 if port or arg_port is not None: 176 base_cmd += ["--port", port or arg_port] 177 if baud or arg_baud is not None: 178 base_cmd += ["--baud", str(baud or arg_baud)] 179 usb_jtag_serial_reset = ["--before", "usb_reset"] if arg_preload_port else [] 180 full_cmd = base_cmd + usb_jtag_serial_reset + args.split(" ") 181 182 # Preload a dummy binary to disable the RTC watchdog, needed in USB-JTAG/Serial 183 if ( 184 preload 185 and arg_preload_port 186 and arg_chip 187 in [ 188 "esp32c3", 189 "esp32s3", 190 "esp32c6", 191 "esp32h2", 192 "esp32p4", 193 "esp32c5", 194 "esp32c61", 195 ] # With U-JS 196 ): 197 port_index = base_cmd.index("--port") + 1 198 base_cmd[port_index] = arg_preload_port # Set the port to the preload one 199 preload_cmd = base_cmd + [ 200 "--no-stub", 201 "load_ram", 202 f"{TEST_DIR}/images/ram_helloworld/helloworld-{arg_chip}.bin", 203 ] 204 print("\nPreloading dummy binary to disable RTC watchdog...") 205 run_esptool_process(preload_cmd) 206 print("Dummy binary preloaded successfully.") 207 time.sleep(0.3) # Wait for the app to run and port to appear 208 209 # Run the command 210 print(f'\nRunning the "{args}" command...') 211 output = run_esptool_process(full_cmd) 212 print(output) # for more complete stdout logs on failure 213 return output 214 215 def run_esptool_error(self, args, baud=None, chip=None): 216 """ 217 Run esptool.py similar to run_esptool, but expect an error. 218 219 Verifies the error is an expected error not an unhandled exception, 220 and returns the output from esptool.py as a string. 221 """ 222 with pytest.raises(subprocess.CalledProcessError) as fail: 223 self.run_esptool(args, baud, chip) 224 failure = fail.value 225 assert failure.returncode in [1, 2] # UnsupportedCmdError and FatalError codes 226 return failure.output.decode("utf-8") 227 228 @classmethod 229 def setup_class(self): 230 print() 231 print(50 * "*") 232 # Save the current working directory to be restored later 233 self.stored_dir = os.getcwd() 234 os.chdir(TEST_DIR) 235 236 @classmethod 237 def teardown_class(self): 238 # Restore the stored working directory 239 os.chdir(self.stored_dir) 240 241 def readback(self, offset, length, spi_connection=None): 242 """Read contents of flash back, return to caller.""" 243 dump_file = tempfile.NamedTemporaryFile(delete=False) # a file we can read into 244 try: 245 cmd = ( 246 f"--before default_reset read_flash {offset} {length} {dump_file.name}" 247 ) 248 if spi_connection: 249 cmd += f" --spi-connection {spi_connection}" 250 self.run_esptool(cmd) 251 with open(dump_file.name, "rb") as f: 252 rb = f.read() 253 254 assert length == len( 255 rb 256 ), f"read_flash length {length} offset {offset:#x} yielded {len(rb)} bytes!" 257 return rb 258 finally: 259 dump_file.close() 260 os.unlink(dump_file.name) 261 262 def diff(self, readback, compare_to): 263 for rb_b, ct_b, offs in zip(readback, compare_to, range(len(readback))): 264 assert ( 265 rb_b == ct_b 266 ), f"First difference at offset {offs:#x} Expected {ct_b} got {rb_b}" 267 268 def verify_readback( 269 self, offset, length, compare_to, is_bootloader=False, spi_connection=None 270 ): 271 rb = self.readback(offset, length, spi_connection) 272 with open(compare_to, "rb") as f: 273 ct = f.read() 274 if len(rb) != len(ct): 275 print( 276 f"WARNING: Expected length {len(ct)} doesn't match comparison {len(rb)}" 277 ) 278 print(f"Readback {len(rb)} bytes") 279 if is_bootloader: 280 # writing a bootloader image to bootloader offset can set flash size/etc, 281 # so don't compare the 8 byte header 282 assert ct[0] == rb[0], "First bytes should be identical" 283 rb = rb[8:] 284 ct = ct[8:] 285 self.diff(rb, ct) 286 287 288@pytest.mark.skipif(arg_chip != "esp32", reason="ESP32 only") 289class TestFlashEncryption(EsptoolTestCase): 290 def valid_key_present(self): 291 try: 292 esp = esptool.ESP32ROM(arg_port) 293 esp.connect() 294 efuses, _ = espefuse.get_efuses(esp=esp) 295 blk1_rd_en = efuses["BLOCK1"].is_readable() 296 return not blk1_rd_en 297 finally: 298 esp._port.close() 299 300 def test_blank_efuse_encrypt_write_abort(self): 301 """ 302 since flash crypt config is not set correctly, this test should abort write 303 """ 304 if self.valid_key_present() is True: 305 pytest.skip("Valid encryption key already programmed, aborting the test") 306 307 self.run_esptool( 308 "write_flash 0x1000 images/bootloader_esp32.bin " 309 "0x8000 images/partitions_singleapp.bin " 310 "0x10000 images/ram_helloworld/helloworld-esp32.bin" 311 ) 312 output = self.run_esptool_error( 313 "write_flash --encrypt 0x10000 images/ram_helloworld/helloworld-esp32.bin" 314 ) 315 assert "Flash encryption key is not programmed".lower() in output.lower() 316 317 def test_blank_efuse_encrypt_write_continue1(self): 318 """ 319 since ignore option is specified, write should happen even though flash crypt 320 config is 0 321 later encrypted flash contents should be read back & compared with 322 precomputed ciphertext 323 pass test 324 """ 325 if self.valid_key_present() is True: 326 pytest.skip("Valid encryption key already programmed, aborting the test") 327 328 self.run_esptool( 329 "write_flash --encrypt --ignore-flash-encryption-efuse-setting " 330 "0x10000 images/ram_helloworld/helloworld-esp32.bin" 331 ) 332 self.run_esptool("read_flash 0x10000 192 images/read_encrypted_flash.bin") 333 self.run_espsecure( 334 "encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " 335 "--flash_crypt_conf 0 --output images/local_enc.bin " 336 "images/ram_helloworld/helloworld-esp32.bin" 337 ) 338 339 try: 340 with open("images/read_encrypted_flash.bin", "rb") as file1: 341 read_file1 = file1.read() 342 343 with open("images/local_enc.bin", "rb") as file2: 344 read_file2 = file2.read() 345 346 for rf1, rf2, i in zip(read_file1, read_file2, range(len(read_file2))): 347 assert ( 348 rf1 == rf2 349 ), f"Encrypted write failed: file mismatch at byte position {i}" 350 351 print("Encrypted write success") 352 finally: 353 os.remove("images/read_encrypted_flash.bin") 354 os.remove("images/local_enc.bin") 355 356 def test_blank_efuse_encrypt_write_continue2(self): 357 """ 358 since ignore option is specified, write should happen even though flash crypt 359 config is 0 360 later encrypted flash contents should be read back & compared with 361 precomputed ciphertext 362 fail test 363 """ 364 if self.valid_key_present() is True: 365 pytest.skip("Valid encryption key already programmed, aborting the test") 366 367 self.run_esptool( 368 "write_flash --encrypt --ignore-flash-encryption-efuse-setting " 369 "0x10000 images/ram_helloworld/helloworld-esp32_edit.bin" 370 ) 371 self.run_esptool("read_flash 0x10000 192 images/read_encrypted_flash.bin") 372 self.run_espsecure( 373 "encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " 374 "--flash_crypt_conf 0 --output images/local_enc.bin " 375 "images/ram_helloworld/helloworld-esp32.bin" 376 ) 377 378 try: 379 with open("images/read_encrypted_flash.bin", "rb") as file1: 380 read_file1 = file1.read() 381 382 with open("images/local_enc.bin", "rb") as file2: 383 read_file2 = file2.read() 384 385 mismatch = any(rf1 != rf2 for rf1, rf2 in zip(read_file1, read_file2)) 386 assert mismatch, "Files should mismatch" 387 388 finally: 389 os.remove("images/read_encrypted_flash.bin") 390 os.remove("images/local_enc.bin") 391 392 393class TestFlashing(EsptoolTestCase): 394 @pytest.mark.quick_test 395 def test_short_flash(self): 396 self.run_esptool("write_flash 0x0 images/one_kb.bin") 397 self.verify_readback(0, 1024, "images/one_kb.bin") 398 399 @pytest.mark.quick_test 400 def test_highspeed_flash(self): 401 self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=921600) 402 self.verify_readback(0, 50 * 1024, "images/fifty_kb.bin") 403 404 def test_adjacent_flash(self): 405 self.run_esptool("write_flash 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") 406 self.verify_readback(0, 4096, "images/sector.bin") 407 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 408 409 def test_short_flash_hex(self): 410 fd, f = tempfile.mkstemp(suffix=".hex") 411 try: 412 self.run_esptool(f"merge_bin --format hex 0x0 images/one_kb.bin -o {f}") 413 # make sure file is closed before running next command (mainly for Windows) 414 os.close(fd) 415 self.run_esptool(f"write_flash 0x0 {f}") 416 self.verify_readback(0, 1024, "images/one_kb.bin") 417 finally: 418 os.unlink(f) 419 420 def test_adjacent_flash_hex(self): 421 fd1, f1 = tempfile.mkstemp(suffix=".hex") 422 fd2, f2 = tempfile.mkstemp(suffix=".hex") 423 try: 424 self.run_esptool(f"merge_bin --format hex 0x0 images/sector.bin -o {f1}") 425 # make sure file is closed before running next command (mainly for Windows) 426 os.close(fd1) 427 self.run_esptool( 428 f"merge_bin --format hex 0x1000 images/fifty_kb.bin -o {f2}" 429 ) 430 os.close(fd2) 431 self.run_esptool(f"write_flash 0x0 {f1} 0x1000 {f2}") 432 self.verify_readback(0, 4096, "images/sector.bin") 433 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 434 finally: 435 os.unlink(f1) 436 os.unlink(f2) 437 438 def test_adjacent_flash_mixed(self): 439 fd, f = tempfile.mkstemp(suffix=".hex") 440 try: 441 self.run_esptool( 442 f"merge_bin --format hex 0x1000 images/fifty_kb.bin -o {f}" 443 ) 444 # make sure file is closed before running next command (mainly for Windows) 445 os.close(fd) 446 self.run_esptool(f"write_flash 0x0 images/sector.bin 0x1000 {f}") 447 self.verify_readback(0, 4096, "images/sector.bin") 448 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 449 finally: 450 os.unlink(f) 451 452 def test_adjacent_independent_flash(self): 453 self.run_esptool("write_flash 0x0 images/sector.bin") 454 self.verify_readback(0, 4096, "images/sector.bin") 455 self.run_esptool("write_flash 0x1000 images/fifty_kb.bin") 456 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 457 # writing flash the second time shouldn't have corrupted the first time 458 self.verify_readback(0, 4096, "images/sector.bin") 459 460 @pytest.mark.skipif( 461 int(os.getenv("ESPTOOL_TEST_FLASH_SIZE", "0")) < 32, reason="needs 32MB flash" 462 ) 463 def test_last_bytes_of_32M_flash(self): 464 flash_size = 32 * 1024 * 1024 465 image_size = 1024 466 offset = flash_size - image_size 467 self.run_esptool("write_flash {} images/one_kb.bin".format(hex(offset))) 468 # Some of the functions cannot handle 32-bit addresses - i.e. addresses accessing 469 # the higher 16MB will manipulate with the lower 16MB flash area. 470 offset2 = offset & 0xFFFFFF 471 self.run_esptool("write_flash {} images/one_kb_all_ef.bin".format(hex(offset2))) 472 self.verify_readback(offset, image_size, "images/one_kb.bin") 473 474 @pytest.mark.skipif( 475 int(os.getenv("ESPTOOL_TEST_FLASH_SIZE", "0")) < 32, reason="needs 32MB flash" 476 ) 477 def test_write_larger_area_to_32M_flash(self): 478 offset = 18 * 1024 * 1024 479 self.run_esptool("write_flash {} images/one_mb.bin".format(hex(offset))) 480 # Some of the functions cannot handle 32-bit addresses - i.e. addresses accessing 481 # the higher 16MB will manipulate with the lower 16MB flash area. 482 offset2 = offset & 0xFFFFFF 483 self.run_esptool("write_flash {} images/one_kb_all_ef.bin".format(hex(offset2))) 484 self.verify_readback(offset, 1 * 1024 * 1024, "images/one_mb.bin") 485 486 def test_correct_offset(self): 487 """Verify writing at an offset actually writes to that offset.""" 488 self.run_esptool("write_flash 0x2000 images/sector.bin") 489 time.sleep(0.1) 490 three_sectors = self.readback(0, 0x3000) 491 last_sector = three_sectors[0x2000:] 492 with open("images/sector.bin", "rb") as f: 493 ct = f.read() 494 assert last_sector == ct 495 496 @pytest.mark.quick_test 497 def test_no_compression_flash(self): 498 self.run_esptool( 499 "write_flash -u 0x0 images/sector.bin 0x1000 images/fifty_kb.bin" 500 ) 501 self.verify_readback(0, 4096, "images/sector.bin") 502 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 503 504 @pytest.mark.quick_test 505 @pytest.mark.skipif(arg_chip == "esp8266", reason="Added in ESP32") 506 def test_compressed_nostub_flash(self): 507 self.run_esptool( 508 "--no-stub write_flash -z 0x0 images/sector.bin 0x1000 images/fifty_kb.bin" 509 ) 510 self.verify_readback(0, 4096, "images/sector.bin") 511 self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") 512 513 def _test_partition_table_then_bootloader(self, args): 514 self.run_esptool(args + " 0x4000 images/partitions_singleapp.bin") 515 self.verify_readback(0x4000, 96, "images/partitions_singleapp.bin") 516 self.run_esptool(args + " 0x1000 images/bootloader_esp32.bin") 517 self.verify_readback(0x1000, 7888, "images/bootloader_esp32.bin", True) 518 self.verify_readback(0x4000, 96, "images/partitions_singleapp.bin") 519 520 def test_partition_table_then_bootloader(self): 521 self._test_partition_table_then_bootloader("write_flash --force") 522 523 def test_partition_table_then_bootloader_no_compression(self): 524 self._test_partition_table_then_bootloader("write_flash --force -u") 525 526 def test_partition_table_then_bootloader_nostub(self): 527 self._test_partition_table_then_bootloader("--no-stub write_flash --force") 528 529 # note: there is no "partition table then bootloader" test that 530 # uses --no-stub and -z, as the ESP32 ROM over-erases and can't 531 # flash this set of files in this order. we do 532 # test_compressed_nostub_flash() instead. 533 534 def test_length_not_aligned_4bytes(self): 535 self.run_esptool("write_flash 0x0 images/not_4_byte_aligned.bin") 536 537 def test_length_not_aligned_4bytes_no_compression(self): 538 self.run_esptool("write_flash -u 0x0 images/not_4_byte_aligned.bin") 539 540 @pytest.mark.quick_test 541 @pytest.mark.host_test 542 def test_write_overlap(self): 543 output = self.run_esptool_error( 544 "write_flash 0x0 images/bootloader_esp32.bin 0x1000 images/one_kb.bin" 545 ) 546 assert "Detected overlap at address: 0x1000 " in output 547 548 @pytest.mark.quick_test 549 @pytest.mark.host_test 550 def test_repeated_address(self): 551 output = self.run_esptool_error( 552 "write_flash 0x0 images/one_kb.bin 0x0 images/one_kb.bin" 553 ) 554 assert "Detected overlap at address: 0x0 " in output 555 556 @pytest.mark.quick_test 557 @pytest.mark.host_test 558 def test_write_sector_overlap(self): 559 # These two 1KB files don't overlap, 560 # but they do both touch sector at 0x1000 so should fail 561 output = self.run_esptool_error( 562 "write_flash 0xd00 images/one_kb.bin 0x1d00 images/one_kb.bin" 563 ) 564 assert "Detected overlap at address: 0x1d00" in output 565 566 def test_write_no_overlap(self): 567 output = self.run_esptool( 568 "write_flash 0x0 images/one_kb.bin 0x2000 images/one_kb.bin" 569 ) 570 assert "Detected overlap at address" not in output 571 572 def test_compressible_file(self): 573 try: 574 input_file = tempfile.NamedTemporaryFile(delete=False) 575 file_size = 1024 * 1024 576 input_file.write(b"\x00" * file_size) 577 input_file.close() 578 self.run_esptool(f"write_flash 0x10000 {input_file.name}") 579 finally: 580 os.unlink(input_file.name) 581 582 def test_compressible_non_trivial_file(self): 583 try: 584 input_file = tempfile.NamedTemporaryFile(delete=False) 585 file_size = 1000 * 1000 586 same_bytes = 8000 587 for _ in range(file_size // same_bytes): 588 input_file.write( 589 struct.pack("B", random.randrange(0, 1 << 8)) * same_bytes 590 ) 591 input_file.close() 592 self.run_esptool(f"write_flash 0x10000 {input_file.name}") 593 finally: 594 os.unlink(input_file.name) 595 596 @pytest.mark.quick_test 597 def test_zero_length(self): 598 # Zero length files are skipped with a warning 599 output = self.run_esptool( 600 "write_flash 0x10000 images/one_kb.bin 0x11000 images/zerolength.bin" 601 ) 602 self.verify_readback(0x10000, 1024, "images/one_kb.bin") 603 assert "zerolength.bin is empty" in output 604 605 @pytest.mark.quick_test 606 def test_single_byte(self): 607 self.run_esptool("write_flash 0x0 images/onebyte.bin") 608 self.verify_readback(0x0, 1, "images/onebyte.bin") 609 610 def test_erase_range_messages(self): 611 output = self.run_esptool( 612 "write_flash 0x1000 images/sector.bin 0x0FC00 images/one_kb.bin" 613 ) 614 assert "Flash will be erased from 0x00001000 to 0x00001fff..." in output 615 assert ( 616 "WARNING: Flash address 0x0000fc00 is not aligned to a 0x1000 " 617 "byte flash sector. 0xc00 bytes before this address will be erased." 618 in output 619 ) 620 assert "Flash will be erased from 0x0000f000 to 0x0000ffff..." in output 621 622 @pytest.mark.skipif( 623 arg_chip == "esp8266", reason="chip_id field exist in ESP32 and later images" 624 ) 625 @pytest.mark.skipif( 626 arg_chip == "esp32s3", reason="This is a valid ESP32-S3 image, would pass" 627 ) 628 def test_write_image_for_another_target(self): 629 output = self.run_esptool_error( 630 "write_flash 0x0 images/esp32s3_header.bin 0x1000 images/one_kb.bin" 631 ) 632 assert "Unexpected chip id in image." in output 633 assert "value was 9. Is this image for a different chip model?" in output 634 assert "images/esp32s3_header.bin is not an " in output 635 assert "image. Use --force to flash anyway." in output 636 637 @pytest.mark.skipif( 638 arg_chip == "esp8266", reason="chip_id field exist in ESP32 and later images" 639 ) 640 @pytest.mark.skipif( 641 arg_chip != "esp32s3", reason="This check happens only on a valid image" 642 ) 643 def test_write_image_for_another_revision(self): 644 output = self.run_esptool_error( 645 "write_flash 0x0 images/one_kb.bin 0x1000 images/esp32s3_header.bin" 646 ) 647 assert "images/esp32s3_header.bin requires chip revision 10" in output 648 assert "or higher (this chip is revision" in output 649 assert "Use --force to flash anyway." in output 650 651 @pytest.mark.skipif( 652 arg_chip != "esp32c3", reason="This check happens only on a valid image" 653 ) 654 def test_flash_with_min_max_rev(self): 655 """Use min/max_rev_full field to specify chip revision""" 656 output = self.run_esptool_error( 657 "write_flash 0x0 images/one_kb.bin 0x1000 images/esp32c3_header_min_rev.bin" 658 ) 659 assert ( 660 "images/esp32c3_header_min_rev.bin " 661 "requires chip revision in range [v2.55 - max rev not set]" in output 662 ) 663 assert "Use --force to flash anyway." in output 664 665 @pytest.mark.quick_test 666 def test_erase_before_write(self): 667 output = self.run_esptool("write_flash --erase-all 0x0 images/one_kb.bin") 668 assert "Chip erase completed successfully" in output 669 assert "Hash of data verified" in output 670 671 @pytest.mark.quick_test 672 def test_flash_not_aligned_nostub(self): 673 output = self.run_esptool("--no-stub write_flash 0x1 images/one_kb.bin") 674 assert ( 675 "WARNING: Flash address 0x00000001 is not aligned to a 0x1000 byte flash sector. 0x1 bytes before this address will be erased." 676 in output 677 ) 678 assert "Hard resetting via RTS pin..." in output 679 680 @pytest.mark.skipif(arg_preload_port is False, reason="USB-JTAG/Serial only") 681 @pytest.mark.skipif(arg_chip != "esp32c3", reason="ESP32-C3 only") 682 def test_flash_overclocked(self): 683 SYSTEM_BASE_REG = 0x600C0000 684 SYSTEM_CPU_PER_CONF_REG = SYSTEM_BASE_REG + 0x008 685 SYSTEM_CPUPERIOD_SEL_S = 0 686 SYSTEM_CPUPERIOD_MAX = 1 # CPU_CLK frequency is 160 MHz 687 688 SYSTEM_SYSCLK_CONF_REG = SYSTEM_BASE_REG + 0x058 689 SYSTEM_SOC_CLK_SEL_S = 10 690 SYSTEM_SOC_CLK_MAX = 1 691 692 output = self.run_esptool( 693 "--after no_reset_stub write_flash 0x0 images/one_mb.bin", preload=False 694 ) 695 faster = re.search(r"(\d+(\.\d+)?)\s+seconds", output) 696 assert faster, "Duration summary not found in the output" 697 698 with esptool.cmds.detect_chip( 699 port=arg_port, connect_mode="no_reset" 700 ) as reg_mod: 701 reg_mod.write_reg( 702 SYSTEM_SYSCLK_CONF_REG, 703 0, 704 mask=(SYSTEM_SOC_CLK_MAX << SYSTEM_SOC_CLK_SEL_S), 705 ) 706 sleep(0.1) 707 reg_mod.write_reg( 708 SYSTEM_CPU_PER_CONF_REG, 709 0, 710 mask=(SYSTEM_CPUPERIOD_MAX << SYSTEM_CPUPERIOD_SEL_S), 711 ) 712 713 output = self.run_esptool( 714 "--before no_reset write_flash 0x0 images/one_mb.bin", preload=False 715 ) 716 slower = re.search(r"(\d+(\.\d+)?)\s+seconds", output) 717 assert slower, "Duration summary not found in the output" 718 assert ( 719 float(slower.group(1)) - float(faster.group(1)) > 1 720 ), "Overclocking failed" 721 722 @pytest.mark.skipif(arg_preload_port is False, reason="USB-JTAG/Serial only") 723 @pytest.mark.skipif(arg_chip != "esp32c3", reason="ESP32-C3 only") 724 def test_flash_watchdogs(self): 725 RTC_WDT_ENABLE = 0xC927FA00 # Valid only for ESP32-C3 726 727 with esptool.cmds.detect_chip(port=arg_port) as reg_mod: 728 # Enable RTC WDT 729 reg_mod.write_reg( 730 reg_mod.RTC_CNTL_WDTWPROTECT_REG, reg_mod.RTC_CNTL_WDT_WKEY 731 ) 732 reg_mod.write_reg(reg_mod.RTC_CNTL_WDTCONFIG0_REG, RTC_WDT_ENABLE) 733 reg_mod.write_reg(reg_mod.RTC_CNTL_WDTWPROTECT_REG, 0) 734 735 # Disable automatic feeding of SWD 736 reg_mod.write_reg( 737 reg_mod.RTC_CNTL_SWD_WPROTECT_REG, reg_mod.RTC_CNTL_SWD_WKEY 738 ) 739 reg_mod.write_reg( 740 reg_mod.RTC_CNTL_SWD_CONF_REG, 0, mask=reg_mod.RTC_CNTL_SWD_AUTO_FEED_EN 741 ) 742 reg_mod.write_reg(reg_mod.RTC_CNTL_SWD_WPROTECT_REG, 0) 743 744 reg_mod.sync_stub_detected = False 745 reg_mod.run_stub() 746 747 output = self.run_esptool( 748 "--before no_reset --after no_reset_stub flash_id", preload=False 749 ) 750 assert "Stub is already running. No upload is necessary." in output 751 752 time.sleep(10) # Wait if RTC WDT triggers 753 754 with esptool.cmds.detect_chip( 755 port=arg_port, connect_mode="no_reset" 756 ) as reg_mod: 757 output = reg_mod.read_reg(reg_mod.RTC_CNTL_WDTCONFIG0_REG) 758 assert output == 0, "RTC WDT is not disabled" 759 760 output = reg_mod.read_reg(reg_mod.RTC_CNTL_SWD_CONF_REG) 761 print(f"RTC_CNTL_SWD_CONF_REG: {output}") 762 assert output & 0x80000000, "SWD auto feeding is not disabled" 763 764 765@pytest.mark.skipif( 766 arg_chip in ["esp8266", "esp32"], 767 reason="get_security_info command is supported on ESP32S2 and later", 768) 769class TestSecurityInfo(EsptoolTestCase): 770 def test_show_security_info(self): 771 res = self.run_esptool("get_security_info") 772 assert "Flags" in res 773 assert "Crypt Count" in res 774 if arg_chip != "esp32c2": 775 assert "Key Purposes" in res 776 if arg_chip != "esp32s2": 777 try: 778 esp = esptool.get_default_connected_device( 779 [arg_port], arg_port, 10, 115200, arg_chip 780 ) 781 assert f"Chip ID: {esp.IMAGE_CHIP_ID}" in res 782 assert "API Version" in res 783 finally: 784 esp._port.close() 785 assert "Secure Boot" in res 786 assert "Flash Encryption" in res 787 788 789class TestFlashSizes(EsptoolTestCase): 790 def test_high_offset(self): 791 self.run_esptool("write_flash -fs 4MB 0x300000 images/one_kb.bin") 792 self.verify_readback(0x300000, 1024, "images/one_kb.bin") 793 794 def test_high_offset_no_compression(self): 795 self.run_esptool("write_flash -u -fs 4MB 0x300000 images/one_kb.bin") 796 self.verify_readback(0x300000, 1024, "images/one_kb.bin") 797 798 def test_large_image(self): 799 self.run_esptool("write_flash -fs 4MB 0x280000 images/one_mb.bin") 800 self.verify_readback(0x280000, 0x100000, "images/one_mb.bin") 801 802 def test_large_no_compression(self): 803 self.run_esptool("write_flash -u -fs 4MB 0x280000 images/one_mb.bin") 804 self.verify_readback(0x280000, 0x100000, "images/one_mb.bin") 805 806 @pytest.mark.quick_test 807 @pytest.mark.host_test 808 def test_invalid_size_arg(self): 809 self.run_esptool_error("write_flash -fs 10MB 0x6000 images/one_kb.bin") 810 811 def test_write_past_end_fails(self): 812 output = self.run_esptool_error( 813 "write_flash -fs 1MB 0x280000 images/one_kb.bin" 814 ) 815 assert "File images/one_kb.bin" in output 816 assert "will not fit" in output 817 818 def test_write_no_compression_past_end_fails(self): 819 output = self.run_esptool_error( 820 "write_flash -u -fs 1MB 0x280000 images/one_kb.bin" 821 ) 822 assert "File images/one_kb.bin" in output 823 assert "will not fit" in output 824 825 @pytest.mark.skipif( 826 arg_chip not in ["esp8266", "esp32", "esp32c3"], 827 reason="Don't run on every chip, so other bootloader images are not needed", 828 ) 829 def test_flash_size_keep(self): 830 offset = 0x1000 if arg_chip in ["esp32", "esp32s2"] else 0x0 831 832 # this image is configured for 2MB (512KB on ESP8266) flash by default. 833 # assume this is not the flash size in use 834 image = f"images/bootloader_{arg_chip}.bin" 835 836 with open(image, "rb") as f: 837 f.seek(0, 2) 838 image_len = f.tell() 839 self.run_esptool(f"write_flash -fs keep {offset} {image}") 840 # header should be the same as in the .bin file 841 self.verify_readback(offset, image_len, image) 842 843 @pytest.mark.skipif( 844 arg_chip == "esp8266", reason="ESP8266 does not support read_flash_slow" 845 ) 846 def test_read_nostub_high_offset(self): 847 offset = 0x300000 848 length = 1024 849 self.run_esptool(f"write_flash -fs detect {offset} images/one_kb.bin") 850 dump_file = tempfile.NamedTemporaryFile(delete=False) 851 # readback with no-stub and flash-size set 852 try: 853 self.run_esptool( 854 f"--no-stub read_flash -fs detect {offset} 1024 {dump_file.name}" 855 ) 856 with open(dump_file.name, "rb") as f: 857 rb = f.read() 858 assert length == len( 859 rb 860 ), f"read_flash length {length} offset {offset:#x} yielded {len(rb)} bytes!" 861 finally: 862 dump_file.close() 863 os.unlink(dump_file.name) 864 # compare files 865 with open("images/one_kb.bin", "rb") as f: 866 ct = f.read() 867 self.diff(rb, ct) 868 869 870class TestFlashDetection(EsptoolTestCase): 871 @pytest.mark.quick_test 872 def test_flash_id(self): 873 """Test manufacturer and device response of flash detection.""" 874 res = self.run_esptool("flash_id") 875 assert "Manufacturer:" in res 876 assert "Device:" in res 877 878 @pytest.mark.quick_test 879 def test_flash_id_expand_args(self): 880 """ 881 Test manufacturer and device response of flash detection with expandable arg 882 """ 883 try: 884 arg_file = tempfile.NamedTemporaryFile(delete=False) 885 arg_file.write(b"flash_id\n") 886 arg_file.close() 887 res = self.run_esptool(f"@{arg_file.name}") 888 assert "Manufacturer:" in res 889 assert "Device:" in res 890 finally: 891 os.unlink(arg_file.name) 892 893 @pytest.mark.quick_test 894 def test_flash_id_trace(self): 895 """Test trace functionality on flash detection, running without stub""" 896 res = self.run_esptool("--trace flash_id") 897 # read register command 898 assert re.search(r"TRACE \+\d.\d{3} command op=0x0a .*", res) is not None 899 # write register command 900 assert re.search(r"TRACE \+\d.\d{3} command op=0x09 .*", res) is not None 901 assert re.search(r"TRACE \+\d.\d{3} Read \d* bytes: .*", res) is not None 902 assert re.search(r"TRACE \+\d.\d{3} Write \d* bytes: .*", res) is not None 903 assert re.search(r"TRACE \+\d.\d{3} Received full packet: .*", res) is not None 904 # flasher stub handshake 905 assert ( 906 re.search(r"TRACE \+\d.\d{3} Received full packet: 4f484149", res) 907 is not None 908 ) 909 assert "Manufacturer:" in res 910 assert "Device:" in res 911 912 @pytest.mark.quick_test 913 @pytest.mark.skipif( 914 arg_chip not in ["esp32c2"], 915 reason="This test make sense only for EPS32-C2", 916 ) 917 def test_flash_size(self): 918 """Test ESP32-C2 efuse block for flash size feature""" 919 # ESP32-C2 class inherits methods from ESP32-C3 class 920 # but it does not have the same amount of efuse blocks 921 # the methods are overwritten 922 # in case anything changes this test will fail to remind us 923 res = self.run_esptool("flash_id") 924 lines = res.splitlines() 925 for line in lines: 926 assert "embedded flash" not in line.lower() 927 928 @pytest.mark.quick_test 929 def test_flash_sfdp(self): 930 """Test manufacturer and device response of flash detection.""" 931 res = self.run_esptool("read_flash_sfdp 0 4") 932 assert "SFDP[0..3]: 53 46 44 50" in res 933 res = self.run_esptool("read_flash_sfdp 1 3") 934 assert "SFDP[1..3]: 46 44 50 " in res 935 936 937@pytest.mark.skipif( 938 os.getenv("ESPTOOL_TEST_SPI_CONN") is None, reason="Needs external flash" 939) 940class TestExternalFlash(EsptoolTestCase): 941 conn = os.getenv("ESPTOOL_TEST_SPI_CONN") 942 943 def test_short_flash_to_external_stub(self): 944 # First flash internal flash, then external 945 self.run_esptool("write_flash 0x0 images/one_kb.bin") 946 self.run_esptool( 947 f"write_flash --spi-connection {self.conn} 0x0 images/sector.bin" 948 ) 949 950 self.verify_readback(0, 1024, "images/one_kb.bin") 951 self.verify_readback(0, 1024, "images/sector.bin", spi_connection=self.conn) 952 953 # First flash external flash, then internal 954 self.run_esptool( 955 f"write_flash --spi-connection {self.conn} 0x0 images/one_kb.bin" 956 ) 957 self.run_esptool("write_flash 0x0 images/sector.bin") 958 959 self.verify_readback(0, 1024, "images/sector.bin") 960 self.verify_readback(0, 1024, "images/one_kb.bin", spi_connection=self.conn) 961 962 def test_short_flash_to_external_ROM(self): 963 # First flash internal flash, then external 964 self.run_esptool("--no-stub write_flash 0x0 images/one_kb.bin") 965 self.run_esptool( 966 f"--no-stub write_flash --spi-connection {self.conn} 0x0 images/sector.bin" 967 ) 968 969 self.verify_readback(0, 1024, "images/one_kb.bin") 970 self.verify_readback(0, 1024, "images/sector.bin", spi_connection=self.conn) 971 972 # First flash external flash, then internal 973 self.run_esptool( 974 f"--no-stub write_flash --spi-connection {self.conn} 0x0 images/one_kb.bin" 975 ) 976 self.run_esptool("--no-stub write_flash 0x0 images/sector.bin") 977 978 self.verify_readback(0, 1024, "images/sector.bin") 979 self.verify_readback(0, 1024, "images/one_kb.bin", spi_connection=self.conn) 980 981 982class TestStubReuse(EsptoolTestCase): 983 def test_stub_reuse_with_synchronization(self): 984 """Keep the flasher stub running and reuse it the next time.""" 985 res = self.run_esptool( 986 "--after no_reset_stub flash_id" 987 ) # flasher stub keeps running after this 988 assert "Manufacturer:" in res 989 res = self.run_esptool( 990 "--before no_reset flash_id", 991 preload=False, 992 ) # do sync before (without reset it talks to the flasher stub) 993 assert "Manufacturer:" in res 994 995 @pytest.mark.skipif(arg_chip != "esp8266", reason="ESP8266 only") 996 def test_stub_reuse_without_synchronization(self): 997 """ 998 Keep the flasher stub running and reuse it the next time 999 without synchronization. 1000 1001 Synchronization is necessary for chips where the ROM bootloader has different 1002 status length in comparison to the flasher stub. 1003 Therefore, this is ESP8266 only test. 1004 """ 1005 res = self.run_esptool("--after no_reset_stub flash_id") 1006 assert "Manufacturer:" in res 1007 res = self.run_esptool("--before no_reset_no_sync flash_id") 1008 assert "Manufacturer:" in res 1009 1010 1011class TestErase(EsptoolTestCase): 1012 @pytest.mark.quick_test 1013 def test_chip_erase(self): 1014 self.run_esptool("write_flash 0x10000 images/one_kb.bin") 1015 self.verify_readback(0x10000, 0x400, "images/one_kb.bin") 1016 self.run_esptool("erase_flash") 1017 empty = self.readback(0x10000, 0x400) 1018 assert empty == b"\xFF" * 0x400 1019 1020 def test_region_erase(self): 1021 self.run_esptool("write_flash 0x10000 images/one_kb.bin") 1022 self.run_esptool("write_flash 0x11000 images/sector.bin") 1023 self.verify_readback(0x10000, 0x400, "images/one_kb.bin") 1024 self.verify_readback(0x11000, 0x1000, "images/sector.bin") 1025 # erase only the flash sector containing one_kb.bin 1026 self.run_esptool("erase_region 0x10000 0x1000") 1027 self.verify_readback(0x11000, 0x1000, "images/sector.bin") 1028 empty = self.readback(0x10000, 0x1000) 1029 assert empty == b"\xFF" * 0x1000 1030 1031 def test_region_erase_all(self): 1032 res = self.run_esptool("erase_region 0x0 ALL") 1033 assert re.search(r"Detected flash size: \d+[KM]B", res) is not None 1034 1035 def test_large_region_erase(self): 1036 # verifies that erasing a large region doesn't time out 1037 self.run_esptool("erase_region 0x0 0x100000") 1038 1039 1040class TestSectorBoundaries(EsptoolTestCase): 1041 def test_end_sector(self): 1042 self.run_esptool("write_flash 0x10000 images/sector.bin") 1043 self.run_esptool("write_flash 0x0FC00 images/one_kb.bin") 1044 self.verify_readback(0x0FC00, 0x400, "images/one_kb.bin") 1045 self.verify_readback(0x10000, 0x1000, "images/sector.bin") 1046 1047 def test_end_sector_uncompressed(self): 1048 self.run_esptool("write_flash -u 0x10000 images/sector.bin") 1049 self.run_esptool("write_flash -u 0x0FC00 images/one_kb.bin") 1050 self.verify_readback(0x0FC00, 0x400, "images/one_kb.bin") 1051 self.verify_readback(0x10000, 0x1000, "images/sector.bin") 1052 1053 def test_overlap(self): 1054 self.run_esptool("write_flash 0x20800 images/sector.bin") 1055 self.verify_readback(0x20800, 0x1000, "images/sector.bin") 1056 1057 1058class TestVerifyCommand(EsptoolTestCase): 1059 @pytest.mark.quick_test 1060 def test_verify_success(self): 1061 self.run_esptool("write_flash 0x5000 images/one_kb.bin") 1062 self.run_esptool("verify_flash 0x5000 images/one_kb.bin") 1063 1064 def test_verify_failure(self): 1065 self.run_esptool("write_flash 0x6000 images/sector.bin") 1066 output = self.run_esptool_error( 1067 "verify_flash --diff=yes 0x6000 images/one_kb.bin" 1068 ) 1069 assert "verify FAILED" in output 1070 assert "first @ 0x00006000" in output 1071 1072 def test_verify_unaligned_length(self): 1073 self.run_esptool("write_flash 0x0 images/not_4_byte_aligned.bin") 1074 self.run_esptool("verify_flash 0x0 images/not_4_byte_aligned.bin") 1075 1076 1077class TestReadIdentityValues(EsptoolTestCase): 1078 @pytest.mark.quick_test 1079 def test_read_mac(self): 1080 output = self.run_esptool("read_mac") 1081 mac = re.search(r"[0-9a-f:]{17}", output) 1082 assert mac is not None 1083 mac = mac.group(0) 1084 assert mac != "00:00:00:00:00:00" 1085 assert mac != "ff:ff:ff:ff:ff:ff" 1086 1087 @pytest.mark.skipif(arg_chip != "esp8266", reason="ESP8266 only") 1088 def test_read_chip_id(self): 1089 output = self.run_esptool("chip_id") 1090 idstr = re.search("Chip ID: 0x([0-9a-f]+)", output) 1091 assert idstr is not None 1092 idstr = idstr.group(1) 1093 assert idstr != "0" * 8 1094 assert idstr != "f" * 8 1095 1096 1097class TestMemoryOperations(EsptoolTestCase): 1098 @pytest.mark.quick_test 1099 def test_memory_dump(self): 1100 output = self.run_esptool("dump_mem 0x50000000 128 memout.bin") 1101 assert "Read 128 bytes" in output 1102 os.remove("memout.bin") 1103 1104 def test_memory_write(self): 1105 output = self.run_esptool("write_mem 0x400C0000 0xabad1dea 0x0000ffff") 1106 assert "Wrote abad1dea" in output 1107 assert "mask 0000ffff" in output 1108 assert "to 400c0000" in output 1109 1110 def test_memory_read(self): 1111 output = self.run_esptool("read_mem 0x400C0000") 1112 assert "0x400c0000 =" in output 1113 1114 1115class TestKeepImageSettings(EsptoolTestCase): 1116 """Tests for the -fm keep, -ff keep options for write_flash""" 1117 1118 @classmethod 1119 def setup_class(self): 1120 super(TestKeepImageSettings, self).setup_class() 1121 self.BL_IMAGE = f"images/bootloader_{arg_chip}.bin" 1122 self.flash_offset = esptool.CHIP_DEFS[arg_chip].BOOTLOADER_FLASH_OFFSET 1123 with open(self.BL_IMAGE, "rb") as f: 1124 self.header = f.read(8) 1125 1126 @pytest.mark.skipif( 1127 arg_chip not in ["esp8266", "esp32", "esp32c3"], 1128 reason="Don't run on every chip, so other bootloader images are not needed", 1129 ) 1130 def test_keep_does_not_change_settings(self): 1131 # defaults should all be keep 1132 self.run_esptool(f"write_flash -fs keep {self.flash_offset:#x} {self.BL_IMAGE}") 1133 self.verify_readback(self.flash_offset, 8, self.BL_IMAGE, False) 1134 # can also explicitly set all options 1135 self.run_esptool( 1136 f"write_flash -fm keep -ff keep -fs keep " 1137 f"{self.flash_offset:#x} {self.BL_IMAGE}" 1138 ) 1139 self.verify_readback(self.flash_offset, 8, self.BL_IMAGE, False) 1140 # verify_flash should also use 'keep' 1141 self.run_esptool( 1142 f"verify_flash -fs keep {self.flash_offset:#x} {self.BL_IMAGE}" 1143 ) 1144 1145 @pytest.mark.skipif( 1146 arg_chip not in ["esp8266", "esp32", "esp32c3"], 1147 reason="Don't run for every chip, so other bootloader images are not needed", 1148 ) 1149 @pytest.mark.quick_test 1150 def test_detect_size_changes_size(self): 1151 self.run_esptool( 1152 f"write_flash -fs detect {self.flash_offset:#x} {self.BL_IMAGE}" 1153 ) 1154 readback = self.readback(self.flash_offset, 8) 1155 assert self.header[:3] == readback[:3] # first 3 bytes unchanged 1156 assert self.header[3] != readback[3] # size_freq byte changed 1157 assert self.header[4:] == readback[4:] # rest unchanged 1158 1159 @pytest.mark.skipif( 1160 arg_chip not in ["esp8266", "esp32"], 1161 reason="Bootloader header needs to be modifiable - without sha256", 1162 ) 1163 def test_explicit_set_size_freq_mode(self): 1164 self.run_esptool( 1165 f"write_flash -fs 2MB -fm dout -ff 80m " 1166 f"{self.flash_offset:#x} {self.BL_IMAGE}" 1167 ) 1168 1169 readback = self.readback(self.flash_offset, 8) 1170 assert self.header[0] == readback[0] 1171 assert self.header[1] == readback[1] 1172 assert (0x3F if arg_chip == "esp8266" else 0x1F) == readback[3] # size_freq 1173 1174 assert 3 != self.header[2] # original image not dout mode 1175 assert 3 == readback[2] # value in flash is dout mode 1176 1177 assert self.header[3] != readback[3] # size/freq values have changed 1178 assert self.header[4:] == readback[4:] # entrypoint address hasn't changed 1179 1180 # verify_flash should pass if we match params, fail otherwise 1181 self.run_esptool( 1182 f"verify_flash -fs 2MB -fm dout -ff 80m " 1183 f"{self.flash_offset:#x} {self.BL_IMAGE}" 1184 ) 1185 self.run_esptool_error(f"verify_flash {self.flash_offset:#x} {self.BL_IMAGE}") 1186 1187 1188@pytest.mark.skipif( 1189 arg_chip in ["esp32s2", "esp32s3", "esp32p4"], 1190 reason="Not supported on targets with USB-CDC.", 1191) 1192class TestLoadRAM(EsptoolTestCase): 1193 # flashing an application not supporting USB-CDC will make 1194 # /dev/ttyACM0 disappear and USB-CDC tests will not work anymore 1195 1196 def verify_output(self, expected_out: List[bytes]): 1197 """Verify that at least one element of expected_out is in serial output""" 1198 # Setting rtscts to true enables hardware flow control. 1199 # This removes unwanted RTS logic level changes for some machines 1200 # (and, therefore, chip resets) 1201 # when the port is opened by the following function. 1202 # As a result, the app loaded to RAM has a chance to run and send 1203 # "Hello world" data without unwanted chip reset. 1204 with serial.serial_for_url(arg_port, arg_baud, rtscts=True) as p: 1205 p.timeout = 5 1206 output = p.read(100) 1207 print(f"Output: {output}") 1208 assert any(item in output for item in expected_out) 1209 1210 @pytest.mark.quick_test 1211 def test_load_ram(self): 1212 """Verify load_ram command 1213 1214 The "hello world" binary programs for each chip print 1215 "Hello world!\n" to the serial port. 1216 """ 1217 self.run_esptool(f"load_ram images/ram_helloworld/helloworld-{arg_chip}.bin") 1218 self.verify_output( 1219 [b"Hello world!", b'\xce?\x13\x05\x04\xd0\x97A\x11"\xc4\x06\xc67\x04'] 1220 ) 1221 1222 def test_load_ram_hex(self): 1223 """Verify load_ram command with hex file as input 1224 1225 The "hello world" binary programs for each chip print 1226 "Hello world!\n" to the serial port. 1227 """ 1228 fd, f = tempfile.mkstemp(suffix=".hex") 1229 try: 1230 self.run_esptool( 1231 f"merge_bin --format hex -o {f} 0x0 " 1232 f"images/ram_helloworld/helloworld-{arg_chip}.bin" 1233 ) 1234 # make sure file is closed before running next command (mainly for Windows) 1235 os.close(fd) 1236 self.run_esptool(f"load_ram {f}") 1237 self.verify_output( 1238 [b"Hello world!", b'\xce?\x13\x05\x04\xd0\x97A\x11"\xc4\x06\xc67\x04'] 1239 ) 1240 finally: 1241 os.unlink(f) 1242 1243 1244class TestDeepSleepFlash(EsptoolTestCase): 1245 @pytest.mark.skipif(arg_chip != "esp8266", reason="ESP8266 only") 1246 def test_deep_sleep_flash(self): 1247 """Regression test for https://github.com/espressif/esptool/issues/351 1248 1249 ESP8266 deep sleep can disable SPI flash chip, 1250 stub loader (or ROM loader) needs to re-enable it. 1251 1252 NOTE: If this test fails, the ESP8266 may need a hard power cycle 1253 (probably with GPIO0 held LOW) to recover. 1254 """ 1255 # not even necessary to wake successfully from sleep, 1256 # going into deep sleep is enough 1257 # (so GPIO16, etc, config is not important for this test) 1258 self.run_esptool("write_flash 0x0 images/esp8266_deepsleep.bin", baud=230400) 1259 1260 time.sleep(0.25) # give ESP8266 time to enter deep sleep 1261 1262 self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=230400) 1263 self.verify_readback(0, 50 * 1024, "images/fifty_kb.bin") 1264 1265 1266class TestBootloaderHeaderRewriteCases(EsptoolTestCase): 1267 @pytest.mark.skipif( 1268 arg_chip not in ["esp8266", "esp32", "esp32c3"], 1269 reason="Don't run on every chip, so other bootloader images are not needed", 1270 ) 1271 @pytest.mark.quick_test 1272 def test_flash_header_rewrite(self): 1273 bl_offset = esptool.CHIP_DEFS[arg_chip].BOOTLOADER_FLASH_OFFSET 1274 bl_image = f"images/bootloader_{arg_chip}.bin" 1275 1276 output = self.run_esptool( 1277 f"write_flash -fm dout -ff 20m {bl_offset:#x} {bl_image}" 1278 ) 1279 if arg_chip in ["esp8266", "esp32"]: 1280 # ESP8266 doesn't support this; The test image for ESP32 just doesn't have it. 1281 assert "Flash params set to" in output 1282 else: 1283 assert "Flash params set to" in output 1284 # Since SHA recalculation is supported for changed bootloader header 1285 assert "SHA digest in image updated" in output 1286 1287 def test_flash_header_no_magic_no_rewrite(self): 1288 # first image doesn't start with magic byte, second image does 1289 # but neither are valid bootloader binary images for either chip 1290 bl_offset = esptool.CHIP_DEFS[arg_chip].BOOTLOADER_FLASH_OFFSET 1291 for image in ["images/one_kb.bin", "images/one_kb_all_ef.bin"]: 1292 output = self.run_esptool( 1293 f"write_flash -fm dout -ff 20m {bl_offset:#x} {image}" 1294 ) 1295 "not changing any flash settings" in output 1296 self.verify_readback(bl_offset, 1024, image) 1297 1298 1299class TestAutoDetect(EsptoolTestCase): 1300 def _check_output(self, output): 1301 expected_chip_name = esptool.util.expand_chip_name(arg_chip) 1302 if arg_chip not in ["esp8266", "esp32", "esp32s2"]: 1303 assert "Unsupported detection protocol" not in output 1304 assert f"Detecting chip type... {expected_chip_name}" in output 1305 assert f"Chip is {expected_chip_name}" in output 1306 1307 @pytest.mark.quick_test 1308 def test_auto_detect(self): 1309 output = self.run_esptool("chip_id", chip="auto") 1310 self._check_output(output) 1311 1312 1313@pytest.mark.flaky(reruns=5) 1314@pytest.mark.skipif(arg_preload_port is not False, reason="USB-to-UART bridge only") 1315@pytest.mark.skipif(os.name == "nt", reason="Linux/MacOS only") 1316class TestVirtualPort(TestAutoDetect): 1317 def test_auto_detect_virtual_port(self): 1318 with ESPRFC2217Server() as server: 1319 output = self.run_esptool( 1320 "chip_id", 1321 chip="auto", 1322 port=f"rfc2217://localhost:{str(server.port)}?ign_set_control", 1323 ) 1324 self._check_output(output) 1325 1326 def test_highspeed_flash_virtual_port(self): 1327 with ESPRFC2217Server() as server: 1328 rfc2217_port = f"rfc2217://localhost:{str(server.port)}?ign_set_control" 1329 self.run_esptool( 1330 "write_flash 0x0 images/fifty_kb.bin", 1331 baud=921600, 1332 port=rfc2217_port, 1333 ) 1334 self.verify_readback(0, 50 * 1024, "images/fifty_kb.bin") 1335 1336 @pytest.fixture 1337 def pty_port(self): 1338 import pty 1339 1340 master_fd, slave_fd = pty.openpty() 1341 yield os.ttyname(slave_fd) 1342 os.close(master_fd) 1343 os.close(slave_fd) 1344 1345 @pytest.mark.host_test 1346 def test_pty_port(self, pty_port): 1347 cmd = [sys.executable, "-m", "esptool", "--port", pty_port, "chip_id"] 1348 output = subprocess.run( 1349 cmd, 1350 cwd=TEST_DIR, 1351 stdout=subprocess.PIPE, 1352 stderr=subprocess.STDOUT, 1353 ) 1354 # no chip connected so command should fail 1355 assert output.returncode != 0 1356 output = output.stdout.decode("utf-8") 1357 print(output) # for logging 1358 assert "WARNING: Chip was NOT reset." in output 1359 1360 1361@pytest.mark.quick_test 1362class TestReadWriteMemory(EsptoolTestCase): 1363 def _test_read_write(self, esp): 1364 # find the start of one of these named memory regions 1365 test_addr = None 1366 for test_region in [ 1367 "RTC_DRAM", 1368 "RTC_DATA", 1369 "DRAM", 1370 ]: # find a probably-unused memory type 1371 region = esp.get_memory_region(test_region) 1372 if region: 1373 if arg_chip == "esp32c61": 1374 # Write into the "BYTE_ACCESSIBLE" space and after the stub 1375 region = esp.get_memory_region("DRAM") 1376 test_addr = region[1] - 0x2FFFF 1377 elif arg_chip == "esp32c2": 1378 # Write at the end of DRAM on ESP32-C2 to avoid overwriting the stub 1379 test_addr = region[1] - 8 1380 else: 1381 test_addr = region[0] 1382 break 1383 1384 print(f"Using test address {test_addr:#x}") 1385 1386 val = esp.read_reg(test_addr) # verify we can read this word at all 1387 1388 try: 1389 esp.write_reg(test_addr, 0x1234567) 1390 assert esp.read_reg(test_addr) == 0x1234567 1391 1392 esp.write_reg(test_addr, 0, delay_us=100) 1393 assert esp.read_reg(test_addr) == 0 1394 1395 esp.write_reg(test_addr, 0x555, delay_after_us=100) 1396 assert esp.read_reg(test_addr) == 0x555 1397 finally: 1398 esp.write_reg(test_addr, val) # write the original value, non-destructive 1399 esp._port.close() 1400 1401 def test_read_write_memory_rom(self): 1402 try: 1403 esp = esptool.get_default_connected_device( 1404 [arg_port], arg_port, 10, 115200, arg_chip 1405 ) 1406 self._test_read_write(esp) 1407 finally: 1408 esp._port.close() 1409 1410 def test_read_write_memory_stub(self): 1411 try: 1412 esp = esptool.get_default_connected_device( 1413 [arg_port], arg_port, 10, 115200, arg_chip 1414 ) 1415 esp = esp.run_stub() 1416 self._test_read_write(esp) 1417 finally: 1418 esp._port.close() 1419 1420 @pytest.mark.skipif( 1421 arg_chip != "esp32", reason="Could be unsupported by different flash" 1422 ) 1423 def test_read_write_flash_status(self): 1424 """Read flash status and write back the same status""" 1425 res = self.run_esptool("read_flash_status") 1426 match = re.search(r"Status value: (0x[\d|a-f]*)", res) 1427 assert match is not None 1428 res = self.run_esptool(f"write_flash_status {match.group(1)}") 1429 assert f"Initial flash status: {match.group(1)}" in res 1430 assert f"Setting flash status: {match.group(1)}" in res 1431 assert f"After flash status: {match.group(1)}" in res 1432 1433 def test_read_chip_description(self): 1434 try: 1435 esp = esptool.get_default_connected_device( 1436 [arg_port], arg_port, 10, 115200, arg_chip 1437 ) 1438 chip = esp.get_chip_description() 1439 assert "unknown" not in chip.lower() 1440 finally: 1441 esp._port.close() 1442 1443 def test_read_get_chip_features(self): 1444 try: 1445 esp = esptool.get_default_connected_device( 1446 [arg_port], arg_port, 10, 115200, arg_chip 1447 ) 1448 1449 if hasattr(esp, "get_flash_cap") and esp.get_flash_cap() == 0: 1450 esp.get_flash_cap = MagicMock(return_value=1) 1451 if hasattr(esp, "get_psram_cap") and esp.get_psram_cap() == 0: 1452 esp.get_psram_cap = MagicMock(return_value=1) 1453 1454 features = ", ".join(esp.get_chip_features()) 1455 assert "Unknown Embedded Flash" not in features 1456 assert "Unknown Embedded PSRAM" not in features 1457 finally: 1458 esp._port.close() 1459 1460 1461@pytest.mark.skipif( 1462 arg_chip != "esp8266", reason="Make image option is supported only on ESP8266" 1463) 1464class TestMakeImage(EsptoolTestCase): 1465 def verify_image(self, offset, length, image, compare_to): 1466 with open(image, "rb") as f: 1467 f.seek(offset) 1468 rb = f.read(length) 1469 with open(compare_to, "rb") as f: 1470 ct = f.read() 1471 if len(rb) != len(ct): 1472 print( 1473 f"WARNING: Expected length {len(ct)} doesn't match comparison {len(rb)}" 1474 ) 1475 print(f"Readback {len(rb)} bytes") 1476 self.diff(rb, ct) 1477 1478 def test_make_image(self): 1479 output = self.run_esptool( 1480 "make_image test" 1481 " -a 0x0 -f images/sector.bin -a 0x1000 -f images/fifty_kb.bin" 1482 ) 1483 try: 1484 assert "Successfully created esp8266 image." in output 1485 assert os.path.exists("test0x00000.bin") 1486 self.verify_image(16, 4096, "test0x00000.bin", "images/sector.bin") 1487 self.verify_image( 1488 4096 + 24, 50 * 1024, "test0x00000.bin", "images/fifty_kb.bin" 1489 ) 1490 finally: 1491 os.remove("test0x00000.bin") 1492 1493 1494@pytest.mark.skipif(arg_chip != "esp32", reason="Don't need to test multiple times") 1495@pytest.mark.quick_test 1496class TestConfigFile(EsptoolTestCase): 1497 class ConfigFile: 1498 """ 1499 A class-based context manager to create 1500 a custom config file and delete it after usage. 1501 """ 1502 1503 def __init__(self, file_path, file_content): 1504 self.file_path = file_path 1505 self.file_content = file_content 1506 1507 def __enter__(self): 1508 with open(self.file_path, "w") as cfg_file: 1509 cfg_file.write(self.file_content) 1510 return cfg_file 1511 1512 def __exit__(self, exc_type, exc_value, exc_tb): 1513 os.unlink(self.file_path) 1514 assert not os.path.exists(self.file_path) 1515 1516 dummy_config = ( 1517 "[esptool]\n" 1518 "connect_attempts = 5\n" 1519 "reset_delay = 1\n" 1520 "serial_write_timeout = 12" 1521 ) 1522 1523 @pytest.mark.host_test 1524 def test_load_config_file(self): 1525 # Test a valid file is loaded 1526 config_file_path = os.path.join(os.getcwd(), "esptool.cfg") 1527 with self.ConfigFile(config_file_path, self.dummy_config): 1528 output = self.run_esptool("version") 1529 assert f"Loaded custom configuration from {config_file_path}" in output 1530 assert "Ignoring unknown config file option" not in output 1531 assert "Ignoring invalid config file" not in output 1532 1533 # Test invalid files are ignored 1534 # Wrong section header, no config gets loaded 1535 with self.ConfigFile(config_file_path, "[wrong section name]"): 1536 output = self.run_esptool("version") 1537 assert f"Loaded custom configuration from {config_file_path}" not in output 1538 1539 # Correct header, but options are unparsable 1540 faulty_config = "[esptool]\n" "connect_attempts = 5\n" "connect_attempts = 9\n" 1541 with self.ConfigFile(config_file_path, faulty_config): 1542 output = self.run_esptool("version") 1543 assert f"Ignoring invalid config file {config_file_path}" in output 1544 assert ( 1545 "option 'connect_attempts' in section 'esptool' already exists" 1546 in output 1547 ) 1548 1549 # Correct header, unknown option (or a typo) 1550 faulty_config = ( 1551 "[esptool]\n" "connect_attempts = 9\n" "timoout = 2\n" "bits = 2" 1552 ) 1553 with self.ConfigFile(config_file_path, faulty_config): 1554 output = self.run_esptool("version") 1555 assert "Ignoring unknown config file options: bits, timoout" in output 1556 1557 # Test other config files (setup.cfg, tox.ini) are loaded 1558 config_file_path = os.path.join(os.getcwd(), "tox.ini") 1559 with self.ConfigFile(config_file_path, self.dummy_config): 1560 output = self.run_esptool("version") 1561 assert f"Loaded custom configuration from {config_file_path}" in output 1562 1563 @pytest.mark.host_test 1564 def test_load_config_file_with_env_var(self): 1565 config_file_path = os.path.join(TEST_DIR, "custom_file.ini") 1566 with self.ConfigFile(config_file_path, self.dummy_config): 1567 # Try first without setting the env var, check that no config gets loaded 1568 output = self.run_esptool("version") 1569 assert f"Loaded custom configuration from {config_file_path}" not in output 1570 1571 # Set the env var and try again, check that config was loaded 1572 tmp = os.environ.get("ESPTOOL_CFGFILE") # Save the env var if it is set 1573 1574 os.environ["ESPTOOL_CFGFILE"] = config_file_path 1575 output = self.run_esptool("version") 1576 assert f"Loaded custom configuration from {config_file_path}" in output 1577 assert "(set with ESPTOOL_CFGFILE)" in output 1578 1579 if tmp is not None: # Restore the env var or unset it 1580 os.environ["ESPTOOL_CFGFILE"] = tmp 1581 else: 1582 os.environ.pop("ESPTOOL_CFGFILE", None) 1583 1584 def test_custom_reset_sequence(self): 1585 # This reset sequence will fail to reset the chip to bootloader, 1586 # the flash_id operation should therefore fail. 1587 # Also tests the number of connection attempts. 1588 reset_seq_config = ( 1589 "[esptool]\n" 1590 "custom_reset_sequence = D0|W0.1|R1|R0|W0.1|R1|R0\n" 1591 "connect_attempts = 1\n" 1592 ) 1593 config_file_path = os.path.join(os.getcwd(), "esptool.cfg") 1594 with self.ConfigFile(config_file_path, reset_seq_config): 1595 output = self.run_esptool_error("flash_id") 1596 assert f"Loaded custom configuration from {config_file_path}" in output 1597 assert "A fatal error occurred: Failed to connect to" in output 1598 # Connection attempts are represented with dots, 1599 # there are enough dots for two attempts here, but only one is executed 1600 assert "Connecting............." not in output 1601 1602 # Test invalid custom_reset_sequence format is not accepted 1603 invalid_reset_seq_config = "[esptool]\n" "custom_reset_sequence = F0|R1|C0|A5\n" 1604 with self.ConfigFile(config_file_path, invalid_reset_seq_config): 1605 output = self.run_esptool_error("flash_id") 1606 assert f"Loaded custom configuration from {config_file_path}" in output 1607 assert 'Invalid "custom_reset_sequence" option format:' in output 1608 1609 def test_open_port_attempts(self): 1610 # Test that the open_port_attempts option is loaded correctly 1611 connect_attempts = 5 1612 config = ( 1613 "[esptool]\n" 1614 f"open_port_attempts = {connect_attempts}\n" 1615 "connect_attempts = 1\n" 1616 "custom_reset_sequence = D0\n" # Invalid reset sequence to make sure connection fails 1617 ) 1618 config_file_path = os.path.join(os.getcwd(), "esptool.cfg") 1619 with self.ConfigFile(config_file_path, config): 1620 output = self.run_esptool_error("flash_id") 1621 assert f"Loaded custom configuration from {config_file_path}" in output 1622 assert "Retrying failed connection" in output 1623 for _ in range(connect_attempts): 1624 assert "Connecting........" in output 1625